Skip to content

Commit 6a423d7

Browse files
committed
Add EmbeddedServerPortFileWriter
Add a EmbeddedServerPortFileWriter which can be used to write server port information to a file. Fixes spring-projectsgh-1275 Closes spring-projectsgh-1491
1 parent 77ccd9a commit 6a423d7

File tree

5 files changed

+313
-27
lines changed

5 files changed

+313
-27
lines changed

spring-boot-actuator/src/main/java/org/springframework/boot/actuate/system/ApplicationPidFileWriter.java

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public ApplicationPidFileWriter(String filename) {
7474
*/
7575
public ApplicationPidFileWriter(File file) {
7676
Assert.notNull(file, "File must not be null");
77-
String override = getOverride();
77+
String override = SystemProperties.get(PROPERTY_VARIABLES);
7878
if (override != null) {
7979
this.file = new File(override);
8080
}
@@ -83,23 +83,6 @@ public ApplicationPidFileWriter(File file) {
8383
}
8484
}
8585

86-
private String getOverride() {
87-
for (String property : PROPERTY_VARIABLES) {
88-
try {
89-
String override = System.getProperty(property);
90-
override = (override != null ? override : System.getenv(property));
91-
if (override != null) {
92-
return override;
93-
}
94-
}
95-
catch (Throwable ex) {
96-
System.err.println("Could not resolve '" + property
97-
+ "' as system property: " + ex);
98-
}
99-
}
100-
return null;
101-
}
102-
10386
@Override
10487
public void onApplicationEvent(ApplicationStartedEvent event) {
10588
if (created.compareAndSet(false, true)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2010-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.system;
18+
19+
import java.io.File;
20+
21+
import org.apache.commons.logging.Log;
22+
import org.apache.commons.logging.LogFactory;
23+
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
24+
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
25+
import org.springframework.context.ApplicationListener;
26+
import org.springframework.util.Assert;
27+
import org.springframework.util.FileCopyUtils;
28+
import org.springframework.util.StringUtils;
29+
30+
/**
31+
* An {@link ApplicationListener} that saves embedded server port and management port into
32+
* file. This application listener will be triggered whenever the servlet container
33+
* starts, and the file name can be overridden at runtime with a System property or
34+
* environment variable named "PORTFILE" or "portfile".
35+
*
36+
* @author David Liu
37+
* @author Phillip Webb
38+
* @since 1.2.0
39+
*/
40+
public class EmbeddedServerPortFileWriter implements
41+
ApplicationListener<EmbeddedServletContainerInitializedEvent> {
42+
43+
private static final String DEFAULT_FILE_NAME = "application.port";
44+
45+
private static final String[] PROPERTY_VARIABLES = { "PORTFILE", "portfile" };
46+
47+
private static final Log logger = LogFactory.getLog(ApplicationPidFileWriter.class);
48+
49+
private final File file;
50+
51+
/**
52+
* Create a new {@link ApplicationPidFileWriter} instance using the filename
53+
* 'application.pid'.
54+
*/
55+
public EmbeddedServerPortFileWriter() {
56+
this.file = new File(DEFAULT_FILE_NAME);
57+
}
58+
59+
/**
60+
* Create a new {@link ApplicationPidFileWriter} instance with a specified filename.
61+
* @param filename the name of file containing pid
62+
*/
63+
public EmbeddedServerPortFileWriter(String filename) {
64+
this(new File(filename));
65+
}
66+
67+
/**
68+
* Create a new {@link ApplicationPidFileWriter} instance with a specified file.
69+
* @param file the file containing pid
70+
*/
71+
public EmbeddedServerPortFileWriter(File file) {
72+
Assert.notNull(file, "File must not be null");
73+
String override = SystemProperties.get(PROPERTY_VARIABLES);
74+
if (override != null) {
75+
this.file = new File(override);
76+
}
77+
else {
78+
this.file = file;
79+
}
80+
}
81+
82+
@Override
83+
public void onApplicationEvent(EmbeddedServletContainerInitializedEvent event) {
84+
File portFile = getPortFile(event.getApplicationContext());
85+
try {
86+
String port = String.valueOf(event.getEmbeddedServletContainer().getPort());
87+
createParentFolder(portFile);
88+
FileCopyUtils.copy(port.getBytes(), portFile);
89+
portFile.deleteOnExit();
90+
}
91+
catch (Exception ex) {
92+
logger.warn(String.format("Cannot create pid file %s", this.file));
93+
}
94+
}
95+
96+
/**
97+
* Return the actual port file that should be written for the given application
98+
* context. The default implementation builds a file from the source file and the
99+
* application context namespace.
100+
* @param applicationContext the source application context
101+
* @return the file that should be written
102+
*/
103+
protected File getPortFile(EmbeddedWebApplicationContext applicationContext) {
104+
String contextName = applicationContext.getNamespace();
105+
if (StringUtils.isEmpty(contextName)) {
106+
return this.file;
107+
}
108+
String name = this.file.getName();
109+
String extension = StringUtils.getFilenameExtension(this.file.getName());
110+
name = name.substring(0, name.length() - extension.length() - 1);
111+
if (isUpperCase(name)) {
112+
name = name + "-" + contextName.toUpperCase();
113+
}
114+
else {
115+
name = name + "-" + contextName.toLowerCase();
116+
}
117+
if (StringUtils.hasLength(extension)) {
118+
name = name + "." + extension;
119+
}
120+
return new File(this.file.getParentFile(), name);
121+
}
122+
123+
private boolean isUpperCase(String name) {
124+
for (int i = 0; i < name.length(); i++) {
125+
if (!Character.isUpperCase(name.charAt(i))) {
126+
return false;
127+
}
128+
}
129+
return true;
130+
}
131+
132+
private void createParentFolder(File file) {
133+
File parent = file.getParentFile();
134+
if (parent != null) {
135+
parent.mkdirs();
136+
}
137+
}
138+
139+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.system;
18+
19+
/**
20+
* @author Phillip Webb
21+
*/
22+
class SystemProperties {
23+
24+
public static String get(String... properties) {
25+
for (String property : properties) {
26+
try {
27+
String override = System.getProperty(property);
28+
override = (override != null ? override : System.getenv(property));
29+
if (override != null) {
30+
return override;
31+
}
32+
}
33+
catch (Throwable ex) {
34+
System.err.println("Could not resolve '" + property
35+
+ "' as system property: " + ex);
36+
}
37+
}
38+
return null;
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2010-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.system;
18+
19+
import java.io.File;
20+
import java.io.FileReader;
21+
22+
import org.junit.After;
23+
import org.junit.Before;
24+
import org.junit.Rule;
25+
import org.junit.Test;
26+
import org.junit.rules.TemporaryFolder;
27+
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
28+
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
29+
import org.springframework.boot.context.embedded.EmbeddedWebApplicationContext;
30+
import org.springframework.util.FileCopyUtils;
31+
import org.springframework.util.StringUtils;
32+
33+
import static org.hamcrest.Matchers.equalTo;
34+
import static org.junit.Assert.assertThat;
35+
import static org.mockito.BDDMockito.given;
36+
import static org.mockito.Mockito.mock;
37+
38+
/**
39+
* Tests {@link EmbeddedServerPortFileWriter}.
40+
*
41+
* @author David Liu
42+
* @author Phillip Webb
43+
*/
44+
public class EmbeddedServerPortFileWriterTests {
45+
46+
@Rule
47+
public TemporaryFolder temporaryFolder = new TemporaryFolder();
48+
49+
@Before
50+
@After
51+
public void reset() {
52+
System.clearProperty("PORTFILE");
53+
}
54+
55+
@Test
56+
public void createPortFile() throws Exception {
57+
File file = this.temporaryFolder.newFile();
58+
EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file);
59+
listener.onApplicationEvent(mockEvent("", 8080));
60+
assertThat(FileCopyUtils.copyToString(new FileReader(file)), equalTo("8080"));
61+
}
62+
63+
@Test
64+
public void overridePortFile() throws Exception {
65+
File file = this.temporaryFolder.newFile();
66+
System.setProperty("PORTFILE", this.temporaryFolder.newFile().getAbsolutePath());
67+
EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file);
68+
listener.onApplicationEvent(mockEvent("", 8080));
69+
assertThat(FileCopyUtils.copyToString(new FileReader(System
70+
.getProperty("PORTFILE"))), equalTo("8080"));
71+
}
72+
73+
@Test
74+
public void createManagementPortFile() throws Exception {
75+
File file = this.temporaryFolder.newFile();
76+
EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file);
77+
listener.onApplicationEvent(mockEvent("", 8080));
78+
listener.onApplicationEvent(mockEvent("management", 9090));
79+
assertThat(FileCopyUtils.copyToString(new FileReader(file)), equalTo("8080"));
80+
String managementFile = file.getName();
81+
managementFile = managementFile.substring(0, managementFile.length()
82+
- StringUtils.getFilenameExtension(managementFile).length() - 1);
83+
managementFile = managementFile + "-management."
84+
+ StringUtils.getFilenameExtension(file.getName());
85+
assertThat(FileCopyUtils.copyToString(new FileReader(new File(file
86+
.getParentFile(), managementFile))), equalTo("9090"));
87+
}
88+
89+
@Test
90+
public void createUpperCaseManagementPortFile() throws Exception {
91+
File file = this.temporaryFolder.newFile();
92+
file = new File(file.getParentFile(), file.getName().toUpperCase());
93+
EmbeddedServerPortFileWriter listener = new EmbeddedServerPortFileWriter(file);
94+
listener.onApplicationEvent(mockEvent("management", 9090));
95+
String managementFile = file.getName();
96+
managementFile = managementFile.substring(0, managementFile.length()
97+
- StringUtils.getFilenameExtension(managementFile).length() - 1);
98+
managementFile = managementFile + "-MANAGEMENT."
99+
+ StringUtils.getFilenameExtension(file.getName());
100+
assertThat(FileCopyUtils.copyToString(new FileReader(new File(file
101+
.getParentFile(), managementFile))), equalTo("9090"));
102+
103+
}
104+
105+
private EmbeddedServletContainerInitializedEvent mockEvent(String name, int port) {
106+
EmbeddedWebApplicationContext applicationContext = mock(EmbeddedWebApplicationContext.class);
107+
EmbeddedServletContainer source = mock(EmbeddedServletContainer.class);
108+
given(applicationContext.getNamespace()).willReturn(name);
109+
given(source.getPort()).willReturn(port);
110+
EmbeddedServletContainerInitializedEvent event = new EmbeddedServletContainerInitializedEvent(
111+
applicationContext, source);
112+
return event;
113+
}
114+
115+
}

spring-boot-docs/src/main/asciidoc/production-ready-features.adoc

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -856,30 +856,38 @@ if needed.
856856

857857
[[production-ready-process-monitoring]]
858858
== Process monitoring
859-
In Spring Boot Actuator you can find `ApplicationPidFileWriter` which creates file
860-
containing application PID (by default in application directory and file name is
861-
`application.pid`). It's not activated by default, but you can do it in two simple
862-
ways described below.
859+
In Spring Boot Actuator you can find a couple of classes to create files that are useful
860+
for process monitoring:
861+
862+
* `ApplicationPidFileWriter` creates a file containing the application PID (by default in
863+
the application directory with the file name `application.pid`).
864+
* `EmbeddedServerPortFileWriter` creates a file (or files) containing the ports of the
865+
embedded server (by default in the application directory with the file name
866+
`application.port`).
867+
868+
These writers are not activated by default, but you can enable them in one of the ways
869+
described below.
863870

864871

865872

866873
[[production-ready-process-monitoring-configuration]]
867874
=== Extend configuration
868-
In `META-INF/spring.factories` file you have to activate the listener:
875+
In `META-INF/spring.factories` file you have to activate the listener(s):
869876

870877
[indent=0]
871878
----
872879
org.springframework.context.ApplicationListener=\
873-
org.springframework.boot.actuate.system.ApplicationPidFileWriter
880+
org.springframework.boot.actuate.system.ApplicationPidFileWriter,
881+
org.springframework.boot.actuate.system.EmbeddedServerPortFileWriter
874882
----
875883

876884

877885

878886
[[production-ready-process-monitoring-programmatically]]
879887
=== Programmatically
880-
You can also activate this listener by invoking `SpringApplication.addListeners(...)`
881-
method and passing `ApplicationPidFileWriter` object. You can also customize file name
882-
and path through constructor.
888+
You can also activate a listener by invoking the `SpringApplication.addListeners(...)`
889+
method and passing the appropriate `Writer` object. This method also allows you to
890+
customize file name and path via the `Writer` constructor.
883891

884892

885893

0 commit comments

Comments
 (0)