001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.context;
018
019import java.io.File;
020import java.io.IOException;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.List;
024import java.util.Locale;
025import java.util.concurrent.atomic.AtomicBoolean;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
031import org.springframework.boot.context.event.ApplicationPreparedEvent;
032import org.springframework.boot.context.event.ApplicationReadyEvent;
033import org.springframework.boot.context.event.SpringApplicationEvent;
034import org.springframework.boot.system.ApplicationPid;
035import org.springframework.boot.system.SystemProperties;
036import org.springframework.context.ApplicationListener;
037import org.springframework.core.Ordered;
038import org.springframework.core.env.Environment;
039import org.springframework.util.Assert;
040
041/**
042 * An {@link ApplicationListener} that saves application PID into file. This application
043 * listener will be triggered exactly once per JVM, and the file name can be overridden at
044 * runtime with a System property or environment variable named "PIDFILE" (or "pidfile")
045 * or using a {@code spring.pid.file} property in the Spring {@link Environment}.
046 * <p>
047 * If PID file can not be created no exception is reported. This behavior can be changed
048 * by assigning {@code true} to System property or environment variable named
049 * {@code PID_FAIL_ON_WRITE_ERROR} (or "pid_fail_on_write_error") or to
050 * {@code spring.pid.fail-on-write-error} property in the Spring {@link Environment}.
051 * <p>
052 * Note: access to the Spring {@link Environment} is only possible when the
053 * {@link #setTriggerEventType(Class) triggerEventType} is set to
054 * {@link ApplicationEnvironmentPreparedEvent}, {@link ApplicationReadyEvent}, or
055 * {@link ApplicationPreparedEvent}.
056 *
057 * @author Jakub Kubrynski
058 * @author Dave Syer
059 * @author Phillip Webb
060 * @author Tomasz Przybyla
061 * @author Madhura Bhave
062 * @since 2.0.0
063 */
064public class ApplicationPidFileWriter
065                implements ApplicationListener<SpringApplicationEvent>, Ordered {
066
067        private static final Log logger = LogFactory.getLog(ApplicationPidFileWriter.class);
068
069        private static final String DEFAULT_FILE_NAME = "application.pid";
070
071        private static final List<Property> FILE_PROPERTIES;
072
073        static {
074                List<Property> properties = new ArrayList<>();
075                properties.add(new SpringProperty("spring.pid.", "file"));
076                properties.add(new SpringProperty("spring.", "pidfile"));
077                properties.add(new SystemProperty("PIDFILE"));
078                FILE_PROPERTIES = Collections.unmodifiableList(properties);
079        }
080
081        private static final List<Property> FAIL_ON_WRITE_ERROR_PROPERTIES;
082
083        static {
084                List<Property> properties = new ArrayList<>();
085                properties.add(new SpringProperty("spring.pid.", "fail-on-write-error"));
086                properties.add(new SystemProperty("PID_FAIL_ON_WRITE_ERROR"));
087                FAIL_ON_WRITE_ERROR_PROPERTIES = Collections.unmodifiableList(properties);
088        }
089
090        private static final AtomicBoolean created = new AtomicBoolean(false);
091
092        private int order = Ordered.HIGHEST_PRECEDENCE + 13;
093
094        private final File file;
095
096        private Class<? extends SpringApplicationEvent> triggerEventType = ApplicationPreparedEvent.class;
097
098        /**
099         * Create a new {@link ApplicationPidFileWriter} instance using the filename
100         * 'application.pid'.
101         */
102        public ApplicationPidFileWriter() {
103                this(new File(DEFAULT_FILE_NAME));
104        }
105
106        /**
107         * Create a new {@link ApplicationPidFileWriter} instance with a specified filename.
108         * @param filename the name of file containing pid
109         */
110        public ApplicationPidFileWriter(String filename) {
111                this(new File(filename));
112        }
113
114        /**
115         * Create a new {@link ApplicationPidFileWriter} instance with a specified file.
116         * @param file the file containing pid
117         */
118        public ApplicationPidFileWriter(File file) {
119                Assert.notNull(file, "File must not be null");
120                this.file = file;
121        }
122
123        /**
124         * Sets the type of application event that will trigger writing of the PID file.
125         * Defaults to {@link ApplicationPreparedEvent}. NOTE: If you use the
126         * {@link org.springframework.boot.context.event.ApplicationStartingEvent} to trigger
127         * the write, you will not be able to specify the PID filename in the Spring
128         * {@link Environment}.
129         * @param triggerEventType the trigger event type
130         */
131        public void setTriggerEventType(
132                        Class<? extends SpringApplicationEvent> triggerEventType) {
133                Assert.notNull(triggerEventType, "Trigger event type must not be null");
134                this.triggerEventType = triggerEventType;
135        }
136
137        @Override
138        public void onApplicationEvent(SpringApplicationEvent event) {
139                if (this.triggerEventType.isInstance(event)
140                                && created.compareAndSet(false, true)) {
141                        try {
142                                writePidFile(event);
143                        }
144                        catch (Exception ex) {
145                                String message = String.format("Cannot create pid file %s", this.file);
146                                if (failOnWriteError(event)) {
147                                        throw new IllegalStateException(message, ex);
148                                }
149                                logger.warn(message, ex);
150                        }
151                }
152        }
153
154        private void writePidFile(SpringApplicationEvent event) throws IOException {
155                File pidFile = this.file;
156                String override = getProperty(event, FILE_PROPERTIES);
157                if (override != null) {
158                        pidFile = new File(override);
159                }
160                new ApplicationPid().write(pidFile);
161                pidFile.deleteOnExit();
162        }
163
164        private boolean failOnWriteError(SpringApplicationEvent event) {
165                String value = getProperty(event, FAIL_ON_WRITE_ERROR_PROPERTIES);
166                return (value != null) ? Boolean.parseBoolean(value) : false;
167        }
168
169        private String getProperty(SpringApplicationEvent event, List<Property> candidates) {
170                for (Property candidate : candidates) {
171                        String value = candidate.getValue(event);
172                        if (value != null) {
173                                return value;
174                        }
175                }
176                return null;
177        }
178
179        public void setOrder(int order) {
180                this.order = order;
181        }
182
183        @Override
184        public int getOrder() {
185                return this.order;
186        }
187
188        /**
189         * Reset the created flag for testing purposes.
190         */
191        protected static void reset() {
192                created.set(false);
193        }
194
195        /**
196         * Provides access to a property value.
197         */
198        private interface Property {
199
200                String getValue(SpringApplicationEvent event);
201
202        }
203
204        /**
205         * {@link Property} obtained from Spring's {@link Environment}.
206         */
207        private static class SpringProperty implements Property {
208
209                private final String prefix;
210
211                private final String key;
212
213                SpringProperty(String prefix, String key) {
214                        this.prefix = prefix;
215                        this.key = key;
216                }
217
218                @Override
219                public String getValue(SpringApplicationEvent event) {
220                        Environment environment = getEnvironment(event);
221                        if (environment == null) {
222                                return null;
223                        }
224                        return environment.getProperty(this.prefix + this.key);
225                }
226
227                private Environment getEnvironment(SpringApplicationEvent event) {
228                        if (event instanceof ApplicationEnvironmentPreparedEvent) {
229                                return ((ApplicationEnvironmentPreparedEvent) event).getEnvironment();
230                        }
231                        if (event instanceof ApplicationPreparedEvent) {
232                                return ((ApplicationPreparedEvent) event).getApplicationContext()
233                                                .getEnvironment();
234                        }
235                        if (event instanceof ApplicationReadyEvent) {
236                                return ((ApplicationReadyEvent) event).getApplicationContext()
237                                                .getEnvironment();
238                        }
239                        return null;
240                }
241
242        }
243
244        /**
245         * {@link Property} obtained from {@link SystemProperties}.
246         */
247        private static class SystemProperty implements Property {
248
249                private final String[] properties;
250
251                SystemProperty(String name) {
252                        this.properties = new String[] { name.toUpperCase(Locale.ENGLISH),
253                                        name.toLowerCase(Locale.ENGLISH) };
254                }
255
256                @Override
257                public String getValue(SpringApplicationEvent event) {
258                        return SystemProperties.get(this.properties);
259                }
260
261        }
262
263}