/*
* Copyright 2011 Matthias van der Vlies
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package core;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import models.Application;
import models.ApplicationProperty;
import play.Logger;
import play.Play;
import play.jobs.Job;
import play.jobs.OnApplicationStart;
import scm.GitVersionControlSystem;
/**
* Manage individual application configurations and load GIT revision on application start
*/
@OnApplicationStart
public class ConfigurationManager extends Job {
/**
* What index is used for container provided properties
*/
public static final int PRIORITY_MARGIN = 100;
/**
* Properties files use ISO-8859-1 encoding
*/
private static final String PROPERTIES_FILE_ENCODING = "ISO-8859-1";
/**
* Comment prepended to exported configuration files
*/
private static final String PROPERTIES_FILE_COMMENT = "Generated by Play! Application Server";
/**
* Get the path for the application.conf file
* @param application The application to generate the path for
*/
private static String getConfigurationFilePath(final Application application) {
return "apps/" + application.pid + "/" + (application.subfolder == null ? "" : application.subfolder) + "conf/application.conf";
}
/**
* Get the path for the log4j.properties file
* @param application The application to generate the path for
*/
private static String getLoggingConfigurationFilePath(final Application application) {
return "apps/" + application.pid + "/" + (application.subfolder == null ? "" : application.subfolder) + "conf/log4j.properties";
}
/**
* Generate configuration files for an application based on the configuration values in the database
* @param application The application to generate files for
*/
public static void generateConfigurationFiles(final Application application) throws IOException {
Logger.info("Creating configuration file for application %s", application.pid);
final Properties properties = new OrderedProperties();
final Properties logProperties = new OrderedProperties();
// #15 update log file
final ApplicationProperty logFileProperty = ApplicationProperty.findLogFileProperty(application);
logFileProperty.value = "logs/" + application.pid + "_" + new SimpleDateFormat("yyyyMMddhhmmss").format(new Date()) + ".log";
logFileProperty.save();
final List<ApplicationProperty> applicationProperties = ApplicationProperty.find("application = ? order by priority", application).fetch();
for(final ApplicationProperty property : applicationProperties) {
// logging
if(property.key.startsWith("log4j")) {
logProperties.setProperty(property.key, property.value);
}
// application.conf
else {
properties.setProperty(property.key, property.value);
}
}
final FileOutputStream outputStream = new FileOutputStream(getConfigurationFilePath(application));
final FileOutputStream logOutputStream = new FileOutputStream(getLoggingConfigurationFilePath(application));
properties.store(outputStream, PROPERTIES_FILE_COMMENT);
logProperties.store(logOutputStream, PROPERTIES_FILE_COMMENT);
outputStream.close();
logOutputStream.close();
}
/**
* Read the current configuration from a string and load it into the database
* @param application The application to load the configuration for
* @param configuration A string with properties file data representation
*/
public static void readCurrentConfigurationFromString(final Application application, final String configuration) throws Exception {
final OrderedProperties properties = new OrderedProperties();
final InputStream inputStream = new ByteArrayInputStream(configuration.getBytes(PROPERTIES_FILE_ENCODING));
properties.load(inputStream);
saveProperties(application, properties);
}
/**
* Read the current configuration and load it into the database
* @param application The application to load the configuration for
*/
public static void readCurrentConfigurationFromFile(final Application application) throws Exception {
final OrderedProperties properties = new OrderedProperties();
final FileInputStream inputStream = new FileInputStream(getConfigurationFilePath(application));
properties.load(inputStream);
saveProperties(application, properties);
// we will already add the logging and mode configuration here!
new ApplicationProperty(application, 1, "application.mode", application.mode.toString()).save();
// logging:
new ApplicationProperty(application, 2, "log4j.rootLogger", "ERROR, Rolling").save();
new ApplicationProperty(application, 3, "log4j.logger.play", "INFO").save();
new ApplicationProperty(application, 4, "log4j.appender.Rolling", "org.apache.log4j.RollingFileAppender").save();
new ApplicationProperty(application, 5, "log4j.appender.Rolling.File", "logs/" + application.pid + ".log").save();
new ApplicationProperty(application, 6, "log4j.appender.Rolling.MaxFileSize", "128KB").save();
new ApplicationProperty(application, 7, "log4j.appender.Rolling.MaxBackupIndex", "100").save();
new ApplicationProperty(application, 8, "log4j.appender.Rolling.layout", "org.apache.log4j.PatternLayout").save();
new ApplicationProperty(application, 9, "log4j.appender.Rolling.layout.ConversionPattern", "%d %-5p ~ %m%n").save();
}
/**
* Save updated configuration for an application
* @param application The application to save the configuration or
* @param properties The updated configuration properties
*/
public static void saveProperties(final Application application, final OrderedProperties properties) {
Logger.info("config(%s): Reading application.conf", application.pid);
// cache existing properties
final Map<String, ApplicationProperty> existingProperties = new HashMap<String, ApplicationProperty>();
if(application.properties != null) {
for(final ApplicationProperty property : application.properties) {
existingProperties.put(property.key, property);
}
}
int priority = PRIORITY_MARGIN + 1; // safe margin
for(final Object rawKey : properties.keySet()) {
final String key = (String) rawKey;
final String value = (String) properties.getProperty(key);
if("application.mode".equals(key) || key.startsWith("log4j")) {
// ignore since the AS is responsible for generating this
continue;
}
ApplicationProperty property = existingProperties.get(key);
if(property == null) {
// new property
property = new ApplicationProperty();
property.application = application;
property.key = key;
}
else {
existingProperties.remove(key);
}
Logger.info("config(%s): %s = %s", application.pid, key, value);
property.value = value;
property.priority = priority;
property.save();
priority++;
}
// remove deleted properties
if(existingProperties.size() > 0) {
for(final ApplicationProperty property : existingProperties.values()) {
if("application.mode".equals(property.key) || property.key.startsWith("log4j")) {
// ignore since the AS is responsible for generating this
continue;
}
property.delete();
Logger.info("config(%s): deleted %s", application.pid, property.key);
}
}
}
@Override
public void doJob() throws Exception {
try {
final String revision = ProcessManager.executeCommand("git-describe", GitVersionControlSystem.getFullGitPath()
+ " describe", new StringBuffer(), false, null, false);
Play.configuration.setProperty("git.revision", revision);
Logger.info("Play! Application Server revision %s", revision);
}
catch(Exception e) {
Play.configuration.setProperty("git.revision", "unknown");
Logger.info("Play! Application Server revision could not be determined");
}
}
}