/*
* This software is distributed under the terms of the FSF
* Gnu Lesser General Public License (see lgpl.txt).
*
* This program is distributed WITHOUT ANY WARRANTY. See the
* GNU General Public License for more details.
*/
package com.scooterframework.admin;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.scooterframework.autoloader.AutoLoaderConfig;
import com.scooterframework.autoloader.ClassWorkHelper;
import com.scooterframework.autoloader.FileMonitor;
import com.scooterframework.common.logging.LogConfig;
import com.scooterframework.common.logging.LogUtil;
import com.scooterframework.common.util.FileUtil;
import com.scooterframework.i18n.I18nConfig;
import com.scooterframework.orm.activerecord.ReferenceDataLoader;
import com.scooterframework.orm.sqldataexpress.config.DatabaseConfig;
import com.scooterframework.orm.sqldataexpress.config.SqlConfig;
import com.scooterframework.web.controller.ActionContext;
import com.scooterframework.web.route.RouteConfig;
/**
* <p>
* ApplicationConfig class configures an application in one of the three modes.
* <ol>
* <li>WEB</li>
* <li>APP</li>
* <li>ORM</li>
* </ol>
* </p>
*
* <p>
* In the <tt>WEB</tt> mode, the application runs in a web container. Its file
* structure follows Scooter's file structure, unless you reset locations of
* some files as specified below.
* </p>
*
* <p>
* In the <tt>APP</tt> mode, the application runs outside of a web container.
* Its file structure follows Scooter's file structure, unless you reset
* locations of some files as specified below.
* Scooter's JUnit test uses this mode.
* </p>
*
* <p>
* In the <tt>ORM</tt> mode, no web container is necessary to use scooter.
* Any application can use Scooter's Active Record or SQL Data Express (SDE)
* capability in their data access layer. This can be achieved by just using
* scooter.jar file and some config property files (<tt>database.properties</tt>,
* <tt>environment.properties</tt>, <tt>log4j.properties</tt>, and
* <tt>sql.properties</tt>). The config files must be on the classpath of the
* running application. </p>
*
* <p>
* In the <tt>ORM</tt> mode, application's file structure does not need to
* follow Scooter's file structure. This is good for those applications that
* want to include Scooter in their own data access layers.
* </p>
*
* <p>See sample stand-alone application <tt>scooter-orm</tt> for more details.</p>
*
* <p>
* For either <tt>WEB</tt> or <tt>APP</tt> mode, application path is
* required. It is automatically set by <tt>WebApplicationStartListener</tt>
* for web application or detected for non-web application.
* </p>
*
* <p>For example, it the application is located in
* <tt>c:\>project\petclinic</tt>, then the application path would
* be '<tt>c:\>project\petclinic</tt>'.
* </p>
*
* <p>In either <tt>WEB</tt> or <tt>APP</tt> mode, the following properties can
* be set through System property from command line. </p>
*
* <pre>
* Configurable System Properties and corresponding default values for web application:
* class.file.location => {app.path}/WEB-INF/classes
* property.file.location => {app.path}/WEB-INF/config
* source.file.location => {app.path}/WEB-INF/src
* reference.file.location => {scooter.home}/lib
* </pre>
*
* @author (Fei) John Chen
*/
public class ApplicationConfig {
private static LogUtil log = null;
public static boolean noConsoleDisplay = false;
public static final String SYSTEM_KEY_CLASSFILE = "class.file.location";
public static final String SYSTEM_KEY_PROPERTYFILE = "property.file.location";
public static final String SYSTEM_KEY_SOURCEFILE = "source.file.location";
public static final String SYSTEM_KEY_PLUGINFILE = "plugin.file.location";
public static final String SYSTEM_KEY_REFERENCEFILE = "reference.file.location";
private static volatile ApplicationConfig me;
private String configuredMode;
private String runningEnvironment = Constants.RUNNING_ENVIRONMENT_DEVELOPMENT;
private ApplicationConfig(String configuredMode, String appPath, String contextName) {
if (appPath == null) {
throw new IllegalArgumentException("Application path cannot be null.");
}
this.applicationPath = appPath;
this.contextName = contextName;
this.configuredMode = configuredMode;
log("Initializing " + configuredMode + " application ... ");
if (Constants.CONFIGURED_MODE_SCOOTER_WEB.equals(configuredMode)) {
log(" context name: " + contextName);
log(" app path: " + applicationPath);
initializeWeb();
}
else if (Constants.CONFIGURED_MODE_SCOOTER_APP.equals(configuredMode)) {
log(" context name: " + contextName);
log(" app path: " + applicationPath);
initializeApp();
}
else if (Constants.CONFIGURED_MODE_SCOOTER_ORM.equals(configuredMode)) {
initializeORM();
}
else {
throw new IllegalArgumentException("Configured mode \"" +
configuredMode + "\" is not supported");
}
}
private void log(String s) {
if (!noConsoleDisplay) System.out.println(s);
}
private void initializePlugins() {
PluginManager.getInstance().startPlugins();
}
/**
* Returns an instance of ApplicationConfig for web application.
*
* This method should be called the first time the application is
* accessed or by a start-up method of a web application.
*/
public static synchronized ApplicationConfig configInstanceForWeb(
String webappPath, String contextName) {
if (me == null) {
me = new ApplicationConfig(Constants.CONFIGURED_MODE_SCOOTER_WEB,
webappPath, contextName);
}
return me;
}
/**
* Returns an instance of ApplicationConfig for non-web application based
* on Scooter's directory structure.
*
* This method should be called in the application start program.
* Subsequent use of ApplicationConfig can then use
* <tt>ApplicationConfig.getInstance()</tt>.
*
* <p>This method assumes the current directory as the application's root
* directory.
* </p>
*/
public static synchronized ApplicationConfig configInstanceForApp() {
if (me == null) {
String path = detectRootPath();
me = new ApplicationConfig(Constants.CONFIGURED_MODE_SCOOTER_APP,
path, detectContextName(path));
}
return me;
}
/**
* Returns an instance of ApplicationConfig for non-web application using
* only Scooter's ORM.
*
* This method should be called in the application start program.
* Subsequent use of ApplicationConfig can then use
* <tt>ApplicationConfig.getInstance()</tt>.
*
* <p>This method assumes that Scooter's config files are on classpath of
* the calling application.
* </p>
*/
public static synchronized ApplicationConfig configInstanceForOrmAlone() {
if (me == null) {
String path = detectRootPath();
me = new ApplicationConfig(Constants.CONFIGURED_MODE_SCOOTER_ORM,
path, detectContextName(path));
}
return me;
}
/**
* Returns an instance of ApplicationConfig. You
* should call either <tt>getInstanceForWeb()</tt> or
* <tt>getInstanceForApp()</tt> or
* <tt>getInstanceForOrmAlone()</tt> method before using this
* <tt>getInstance()</tt> method.
*/
public static ApplicationConfig getInstance() {
if (me == null) {
throw new IllegalArgumentException("You should use either " +
"'getInstanceForWeb()' or 'getInstanceForApp()' or " +
"'getInstanceForOrmAlone()' method before " +
"using this 'getInstance()' method. If this is a web app, check your context path.");
}
return me;
}
public void startORMApplication() {
if (applicationStarted) return;
applicationStartTime = System.currentTimeMillis();
configInstanceForOrmAlone();
LogUtil.enableLogger();
try {
EnvConfig.getInstance();
}
catch(NoClassDefFoundError er) {
if (!"org/apache/commons/fileupload/FileItemFactory".equals(er.getMessage())) {
log.error("Error in EnvConfig.getInstance()", er);
}
}
DatabaseConfig dbc = DatabaseConfig.getInstance();
SqlConfig.getInstance();
//
//store some important information about the server
//
Map<String, Object> props = new HashMap<String, Object>();
props.put(Constants.APP_KEY_JAVA_VERSION, System.getProperty("java.version"));
props.put(Constants.APP_KEY_SCOOTER_VERSION, Version.CURRENT_VERSION);
props.put(Constants.APP_KEY_RUNNING_ENVIRONMENT, runningEnvironment);
props.put(Constants.APP_KEY_APPLICATION_START_TIME, new Date(applicationStartTime));
props.put(Constants.APP_KEY_APPLICATION_ROOT_PATH, applicationPath);
props.put(Constants.APP_KEY_APPLICATION_CONTEXT_NAME, contextName);
props.put(Constants.APP_KEY_APPLICATION_DATABASE_NAME, dbc.getDefaultDatabaseConnectionName());
ActionContext.storeToGlobal(Constants.APP_KEY_SCOOTER_PROPERTIES, props);
initializePlugins();
applicationStarted = true;
}
public void startApplication() {
if (applicationStarted) return;
applicationStartTime = System.currentTimeMillis();
if (isWebApp()) {
PropertyFileChangeMonitor.getInstance();
}
//need to do the following:
logConfig.enableMonitoring();
EnvConfig.getInstance();
if (isInDevelopmentEnvironment() && isWebApp()) {
PropertyFileChangeMonitor.getInstance().start();
}
AutoLoaderConfig.getInstance();
if (isInDevelopmentEnvironment() && isWebApp()) {
FileMonitor.getInstance().start();
}
DatabaseConfig dbc = DatabaseConfig.getInstance();
SqlConfig.getInstance();
if (isWebApp()) RouteConfig.getInstance();
if (isWebApp() && !ReferenceDataLoader.isStarted()) {
rdLoader = new ReferenceDataLoader();
rdLoader.start();
}
I18nConfig.getInstance();
if (isInDevelopmentEnvironment() && isWebApp()) {
DirChangeMonitor.getInstance().start();
}
//
//store some important information about the server
//
Map<String, Object> props = new HashMap<String, Object>();
props.put(Constants.APP_KEY_JAVA_VERSION, System.getProperty("java.version"));
props.put(Constants.APP_KEY_SCOOTER_VERSION, Version.CURRENT_VERSION);
props.put(Constants.APP_KEY_RUNNING_ENVIRONMENT, runningEnvironment);
props.put(Constants.APP_KEY_APPLICATION_START_TIME, new Date(applicationStartTime));
props.put(Constants.APP_KEY_APPLICATION_ROOT_PATH, applicationPath);
props.put(Constants.APP_KEY_APPLICATION_CONTEXT_NAME, contextName);
props.put(Constants.APP_KEY_APPLICATION_DATABASE_NAME, dbc.getDefaultDatabaseConnectionName());
ActionContext.storeToGlobal(Constants.APP_KEY_SCOOTER_PROPERTIES, props);
if(isApp() && "true".equals(System.getProperty(Constants.ALLOW_CLASSWORK))) {
try {
ClassWorkHelper.preloadClasses(sourceFileLocationPath);
} catch (Exception ex) {
log.error("Error in preloadClasses", ex);
}
}
initializePlugins();
applicationStarted = true;
if (log != null) {
String startMsg = "Application started in " + runningEnvironment +
" with Scooter version: " + Version.CURRENT_VERSION;
log.info(startMsg);
if (log.isLogLevelGreaterThanInfo()) {
System.out.println(startMsg);
}
}
}
public void endApplication() {
if (rdLoader != null && ReferenceDataLoader.isStarted()) {
rdLoader.stop();
}
PropertyFileChangeMonitor.getInstance().stop();
FileMonitor.getInstance().stop();
//started by I18nConfig
DirChangeMonitor.getInstance().stop();
DatabaseConfig.getInstance().destroy();
applicationStarted = false;
PluginManager.getInstance().stopPlugins();
if (log != null) log.info("Application ended.");
}
public static String detectRootPath() {
String path = "";
try {
path = (new File("")).getCanonicalPath();
}
catch(Exception ex) {
ex.printStackTrace();
String errorMessage = "Failed to detect root path from current directory: " + ex.getMessage();
System.err.println(errorMessage);
System.out.println("Stop initializtion process. Exit now ...");
}
return path;
}
/**
* Returns number of sessions in the container.
*/
public static int getSessionCount() {
return WebSessionListener.getSessionCount();
}
public long getApplicationStartTime() {
return applicationStartTime;
}
public String getApplicationPath() {
return applicationPath;
}
public String getContextName() {
return contextName;
}
public boolean applicationStarted() {
return applicationStarted;
}
public String getConfiguredMode() {
return configuredMode;
}
/**
* Return true if the current app is configured as a web app.
*/
public boolean isWebApp() {
return Constants.CONFIGURED_MODE_SCOOTER_WEB.equals(configuredMode);
}
/**
* Return true if the current app is configured as a regular app.
*/
public boolean isApp() {
return Constants.CONFIGURED_MODE_SCOOTER_APP.equals(configuredMode);
}
/**
* Return true if the current app is configured to use orm alone.
*/
public boolean isOrmAlone() {
return Constants.CONFIGURED_MODE_SCOOTER_ORM.equals(configuredMode);
}
/**
* Returns running environment
*/
public String getRunningEnvironment() {
return runningEnvironment;
}
public void setRunningEnvironment(String runningEnvironment) {
this.runningEnvironment = runningEnvironment;
}
/**
* Checks if the current running environment is development environment.
*
* @return true if the current running environment is development.
*/
public boolean isInDevelopmentEnvironment() {
return Constants.RUNNING_ENVIRONMENT_DEVELOPMENT.equals(runningEnvironment);
}
/**
* Checks if the current running environment is test environment.
*
* @return true if the current running environment is test.
*/
public boolean isInTestEnvironment() {
return Constants.RUNNING_ENVIRONMENT_TEST.equals(runningEnvironment);
}
/**
* Checks if the current running environment is production environment.
*
* @return true if the current running environment is production.
*/
public boolean isInProductionEnvironment() {
return Constants.RUNNING_ENVIRONMENT_PRODUCTION.equals(runningEnvironment);
}
public String getClassFileLocationPath() {
return classFileLocationPath;
}
public String getPropertyFileLocationPath() {
return propertyFileLocationPath;
}
public String getSourceFileLocationPath() {
return sourceFileLocationPath;
}
public String getWebappLibPath() {
return webappLibPath;
}
public String getPluginsPath() {
return pluginsPath;
}
public String getReferencesLibPath() {
return referencesLibPath;
}
public void setClassFileLocationPath(String classFileLocationPath) {
this.classFileLocationPath = classFileLocationPath;
}
public void setPropertyFileLocationPath(String propertyFileLocationPath) {
this.propertyFileLocationPath = propertyFileLocationPath;
}
public void setSourceFileLocationPath(String sourceFileLocationPath) {
this.sourceFileLocationPath = sourceFileLocationPath;
}
private void initializeWeb() {
webappLibPath = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "lib";
String cfl = System.getProperty(SYSTEM_KEY_CLASSFILE, "");
if ("".equals(cfl)) {
cfl = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "classes";
}
classFileLocationPath = cfl;
String pfl = System.getProperty(SYSTEM_KEY_PROPERTYFILE, "");
if ("".equals(pfl)) {
pfl = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "config";
}
propertyFileLocationPath = pfl;
String sfl = System.getProperty(SYSTEM_KEY_SOURCEFILE, "");
if ("".equals(sfl)) {
String srcPath = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "src";
String unitTestPath = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "test"
+ File.separatorChar + "unit";
String functionalTestPath = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "test"
+ File.separatorChar + "functional";
sfl = srcPath + File.pathSeparatorChar + unitTestPath
+ File.pathSeparatorChar + functionalTestPath;
}
sourceFileLocationPath = sfl;
String pgl = System.getProperty(SYSTEM_KEY_PLUGINFILE, "");
if ("".equals(pgl)) {
String sh = System.getProperty("scooter.home");
if (sh != null) {
pgl = sh + File.separatorChar + "plugins";
}
}
pluginsPath = pgl;
String rfl = System.getProperty(SYSTEM_KEY_REFERENCEFILE, "");
if ("".equals(rfl)) {
rfl = System.getProperty("scooter.home") + File.separatorChar + "lib";
if (!FileUtil.pathExistAndHasFiles(rfl)) {
rfl = applicationPath
+ File.separatorChar + "WEB-INF"
+ File.separatorChar + "lib";
}
}
referencesLibPath = rfl;
log(" " + SYSTEM_KEY_CLASSFILE + ": " + classFileLocationPath);
log(" " + SYSTEM_KEY_PROPERTYFILE + ": " + propertyFileLocationPath);
log(" " + SYSTEM_KEY_SOURCEFILE + ": " + sourceFileLocationPath);
log("" + SYSTEM_KEY_REFERENCEFILE + ": " + referencesLibPath);
if (pluginsPath != null && !"".equals(pluginsPath)) {
log(" " + SYSTEM_KEY_PLUGINFILE + ": " + pluginsPath);
}
else {
log("INFO: If jars under scooter/plugins are needed, use -Dscooter.home=... to setup path to the plugin directory.");
}
//make sure there are files in the property file location
String[] fileNames = (new File(propertyFileLocationPath)).list();
if (fileNames == null || fileNames.length == 0) {
log("ERROR ERROR ERROR => There are no property files in the property file location.");
log("Stop initializtion process. Exit now ...");
}
//initialized other stuff if not started
logConfig = LogConfig.getInstance(propertyFileLocationPath);
log = LogUtil.getLogger(ApplicationConfig.class.getName());
}
private void initializeApp() {
webappLibPath = applicationPath + File.separatorChar + "lib";
String cfl = System.getProperty(SYSTEM_KEY_CLASSFILE, "");
if ("".equals(cfl)) {
cfl = applicationPath + File.separatorChar + "build"
+ File.separatorChar + "classes";
}
classFileLocationPath = cfl;
String pfl = System.getProperty(SYSTEM_KEY_PROPERTYFILE, "");
if ("".equals(pfl)) {
pfl = applicationPath + File.separatorChar + "source"
+ File.separatorChar + "test"
+ File.separatorChar + "config";
}
propertyFileLocationPath = pfl;
String sfl = System.getProperty(SYSTEM_KEY_SOURCEFILE, "");
if ("".equals(sfl)) {
sfl = applicationPath + File.separatorChar + "source"
+ File.separatorChar + "test";
}
sourceFileLocationPath = sfl;
String pgl = System.getProperty(SYSTEM_KEY_PLUGINFILE, "");
if ("".equals(pgl)) {
pgl = System.getProperty("scooter.home") + File.separatorChar + "plugins";
}
pluginsPath = pgl;
String rfl = System.getProperty(SYSTEM_KEY_REFERENCEFILE, "");
if ("".equals(rfl)) {
rfl = webappLibPath;
}
referencesLibPath = rfl;
//log(" " + SYSTEM_KEY_CLASSFILE + ": " + classFileLocationPath);
log(" " + SYSTEM_KEY_PROPERTYFILE + ": " + propertyFileLocationPath);
//log(" " + SYSTEM_KEY_SOURCEFILE + ": " + sourceFileLocationPath);
log("" + SYSTEM_KEY_REFERENCEFILE + ": " + referencesLibPath);
//make sure there are files in the property file location
String[] fileNames = (new File(propertyFileLocationPath)).list();
if (fileNames == null || fileNames.length == 0) {
log("ERROR ERROR ERROR => There are no property files in the property file location.");
log("Stop initializtion process. Exit now ...");
}
if (!FileUtil.pathExistAndHasFiles(referencesLibPath)) {
log("ERROR ERROR ERROR => There are no jar files in the lib location.");
log("Stop initializtion process. Exit now ...");
}
//initialized other stuff if not started
logConfig = LogConfig.getInstance(propertyFileLocationPath);
log = LogUtil.getLogger(ApplicationConfig.class.getName());
}
private void initializeORM() {
//initialized other stuff if not started
log = LogUtil.getLogger(ApplicationConfig.class.getName());
LogUtil.enableLogger();
}
private static String detectContextName(String path) {
if (path.endsWith(File.separator)) path = path.substring(0, path.length() -1);
return path.substring(path.lastIndexOf(File.separatorChar) + 1);
}
private LogConfig logConfig;
private long applicationStartTime = 0L;
private boolean applicationStarted;
private String applicationPath;
private String contextName;
private String classFileLocationPath;
private String propertyFileLocationPath;
private String sourceFileLocationPath;
private String webappLibPath;
private String pluginsPath;
private String referencesLibPath;
private ReferenceDataLoader rdLoader = null;
}