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}