/*
* Copyright (C) 2000 - 2013 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://openbd.org/
* $Id: cfApplicationManager.java 2331 2013-02-25 20:07:43Z alan $
*/
package com.naryx.tagfusion.cfm.application;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.lang.time.DateUtils;
import org.aw20.util.SystemClockEvent;
import com.naryx.tagfusion.cfm.engine.cfComponentData;
import com.naryx.tagfusion.cfm.engine.cfData;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.cfm.engine.cfcMethodData;
import com.naryx.tagfusion.cfm.engine.cfmAbortException;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.engine.engineListener;
import com.naryx.tagfusion.cfm.file.cfFile;
import com.naryx.tagfusion.cfm.tag.cfTag;
import com.naryx.tagfusion.util.dummyServletRequest;
import com.naryx.tagfusion.util.dummyServletResponse;
import com.naryx.tagfusion.xmlConfig.xmlCFML;
public class cfApplicationManager extends Object implements java.io.Serializable, engineListener, SystemClockEvent {
static final long serialVersionUID = 1;
public static final String DEFAULT_CLIENT_STORAGE = "COOKIE";
public static final String DEFAULT_CLIENT_PURGE = "true";
public static final String DEFAULT_CLIENT_EXPIRY = "90";
public static final String DEFAULT_SESSION_TIMEOUT = "#CreateTimeSpan(0,0,20,0)#"; // "0.01388";
public static final String DEFAULT_APPLICATION_TIMEOUT = "#CreateTimeSpan(0,1,0,0)#"; // "1";
public static final String DEFAULT_J2EE_SESSION = "false";
public static final String DEFAULT_CF5CLIENTDATA = "false";
public static final String DEFAULT_GLOBAL_CLIENT_UPDATES_DISABLED = "false";
public static final String DEFAULT_CFTOKEN_UUID = "true";
private static final String ON_APPLICATION_END = "onApplicationEnd";
public static boolean cf5ClientData;
public static boolean clientGlobalUpdateDisabled;
public static boolean cftokenUUID;
private String sessionTimeOut, applicationTimeOut, defaultClientStorage;
private cfData sessionTimeOutData, applicationTimeOutData;
private boolean bJ2EESessionManagement;
// --------------------------------------------------
public static cfApplicationManager init(xmlCFML config) {
cfApplicationManager appManager = new cfApplicationManager();
appManager.engineAdminUpdate(config);
cfEngine.registerEngineListener(appManager);
cfEngine.thisPlatform.timerSetListenerMinute(appManager, 5);
cf5ClientData = config.getBoolean("server.cfapplication.cf5clientdata", false);
if (cf5ClientData) {
cfEngine.log("ColdFusion-compatible CLIENT data enabled");
cftokenUUID = config.getBoolean("server.cfapplication.cftokenuuid", false); // default to false
} else {
cftokenUUID = config.getBoolean("server.cfapplication.cftokenuuid", true); // default to true
}
// Initialise this watchdog that will look for the deletion of the client data
new cfClientDataManager();
return appManager;
}
public void engineShutdown() {
onApplicationEnd(false); // false == don't check if expired
cfEngine.log("cfApplicationManager has been shutdown");
}
public int getApplicationCount() {
// Run through the Servlet Context counting the applications
int totalApps = 0;
Enumeration<String> E = cfEngine.thisServletContext.getAttributeNames();
while (E.hasMoreElements()) {
String key = E.nextElement();
if ((cfEngine.thisServletContext.getAttribute(key)) instanceof cfApplicationData) {
totalApps++;
}
}
return totalApps;
}
public int getSessionCount(String name){
Object o = cfEngine.thisServletContext.getAttribute(name);
if ( o != null && o instanceof cfApplicationData ){
return ((cfApplicationData)o).getTotalSessions();
}
return -1;
}
public int getSessionCount() {
int totalSessions = 0;
Enumeration<String> E = cfEngine.thisServletContext.getAttributeNames();
while (E.hasMoreElements()) {
String key = E.nextElement();
if ((cfEngine.thisServletContext.getAttribute(key)) instanceof cfApplicationData) {
totalSessions += ((cfApplicationData) cfEngine.thisServletContext.getAttribute(key)).getTotalSessions();
}
}
return totalSessions;
}
// --------------------------------------------------
// Methods to support the default values of the Application and Session values
// --------------------------------------------------
public void engineAdminUpdate(xmlCFML config) {
// Defaults to store the client data using COOKIES
defaultClientStorage = config.getString("server.cfapplication.clientstorage", DEFAULT_CLIENT_STORAGE);
if (defaultClientStorage.equalsIgnoreCase("REGISTRY")) {
cfEngine.log("ERROR: Registry can no longer be used to store client data. Cookies will be used instead.");
defaultClientStorage = DEFAULT_CLIENT_STORAGE;
config.setData("server.cfapplication.clientstorage", DEFAULT_CLIENT_STORAGE);
try {
cfEngine.writeXmlFile(config, false);
} catch (cfmRunTimeException rte) {
cfEngine.log("ERROR: failed to change registry to cookie in bluedragon.xml.");
}
}
clientGlobalUpdateDisabled = config.getBoolean("server.cfapplication.clientglobalupdatesdisabled", false);
if (clientGlobalUpdateDisabled) {
cfEngine.log("Global client variable updates disabled");
}
// Defaults to not use the J2EE Session management
bJ2EESessionManagement = config.getBoolean("server.cfapplication.j2eesession", Boolean.valueOf(DEFAULT_J2EE_SESSION).booleanValue());
if (bJ2EESessionManagement) {
/*
* Set to -1 so it will default to the session timeout value
* configured in the J2EE web app's web.xml file
*/
sessionTimeOut = "-1";
} else {
// Defaults to 20 minutes, expressed as a fraction of 1 day
sessionTimeOut = config.getString("server.cfapplication.sessiontimeout", DEFAULT_SESSION_TIMEOUT);
}
sessionTimeOutData = null;
// Defaults to 2 days
applicationTimeOut = config.getString("server.cfapplication.applicationtimeout", DEFAULT_APPLICATION_TIMEOUT);
applicationTimeOutData = null;
cfEngine.log("cfApplicationManager.DefaultClientStorage=[" + defaultClientStorage + "]; J2EE Sessions=" + bJ2EESessionManagement );
}
public String getDefaultSessionTimeOut() {
return sessionTimeOut;
}
public cfData getDefaultSessionTimeOutData(cfSession session) throws cfmRunTimeException {
if (sessionTimeOutData == null)
sessionTimeOutData = cfTag.getDynamicAttribute(session, sessionTimeOut);
return sessionTimeOutData;
}
public String getDefaultApplicationTimeOut() {
return applicationTimeOut;
}
public cfData getDefaultApplicationTimeOutData(cfSession session) throws cfmRunTimeException {
if (applicationTimeOutData == null)
applicationTimeOutData = cfTag.getDynamicAttribute(session, applicationTimeOut);
return applicationTimeOutData;
}
public String getDefaultClientStorage() {
return defaultClientStorage;
}
// --------------------------------------------------
public synchronized void clockEvent(int type) {
onApplicationEnd(true); // true == only if expired
}
private void onApplicationEnd(boolean checkExpired) {
List<String> attrNames = getAttributeNames();
for (int i = 0; i < attrNames.size(); i++) {
String key = attrNames.get(i);
Object attr = cfEngine.thisServletContext.getAttribute(key);
if (attr instanceof cfApplicationData) {
cfApplicationData appData = (cfApplicationData) attr;
// the expire() method only returns true if there are no active sessions
if (checkExpired && !appData.expire()) {
continue;
}
// invoke onSessionEnd() for all sessions within the application
appData.onApplicationEnd();
cfEngine.thisServletContext.removeAttribute(key);
onApplicationEnd(appData);
cfEngine.log("cfApplicationManager.onApplicationEnd: " + key);
}
}
if (!checkExpired) { // J2EE applications never expire
onApplicationEnd(new cfJ2EEApplicationData(cfEngine.thisServletContext));
}
}
private static List<String> getAttributeNames() {
/*
* With WebLogic Server, removing an attribute while enumerating over the
* attribute names will result in a ConcurrentModificationException so we need to copy
* the attribute names to a Vector and loop over it instead.
*/
Enumeration<String> E = cfEngine.thisServletContext.getAttributeNames();
List<String> attrNames = new ArrayList<String>();
while (E.hasMoreElements())
attrNames.add(E.nextElement());
return attrNames;
}
// --------------------------------------------------
public void loadApplication(cfComponentData applicationCfc, cfSession session) throws cfmRunTimeException {
cfData appName = applicationCfc.getData("NAME");
cfApplicationData appData = getAppData(session, appName == null ? cfAPPLICATION.UNNAMED_APPNAME : appName.toString());
String scriptProtect = null;
if (applicationCfc.containsKey(cfAPPLICATION.SCRIPTPROTECT)) {
scriptProtect = applicationCfc.getData(cfAPPLICATION.SCRIPTPROTECT).getString();
}
// Set up the specific application mappings
if (applicationCfc.containsKey(cfAPPLICATION.MAPPINGS)) {
cfData mappingsData = (cfData) applicationCfc.getData(cfAPPLICATION.MAPPINGS);
if (mappingsData.getDataType() == cfData.CFSTRUCTDATA) {
session.setDataBin(cfAPPLICATION.MAPPINGS, mappingsData);
}
}
// Set up the specific application customtagpaths
if (applicationCfc.containsKey(cfAPPLICATION.CUSTOMTAGPATHS)) {
cfData mappingsData = (cfData) applicationCfc.getData(cfAPPLICATION.CUSTOMTAGPATHS);
if (mappingsData.getDataType() == cfData.CFSTRINGDATA) {
session.setDataBin(cfAPPLICATION.CUSTOMTAGPATHS, mappingsData.getString() );
}
}
String datasource = null;
if ( applicationCfc.containsKey(cfAPPLICATION.DATASOURCE))
datasource = applicationCfc.getData(cfAPPLICATION.DATASOURCE).getString();
String sessionstorage = null;
if ( applicationCfc.containsKey(cfAPPLICATION.SESSIONSTORAGE))
sessionstorage = applicationCfc.getData(cfAPPLICATION.SESSIONSTORAGE).getString();
appData.onRequestStart(session, bJ2EESessionManagement,
(long) (applicationCfc.getData(cfAPPLICATION.APPLICATIONTIMEOUT).getDouble() * DateUtils.MILLIS_PER_DAY),
(long) (applicationCfc.getData(cfAPPLICATION.SESSIONTIMEOUT).getDouble() * DateUtils.MILLIS_PER_DAY),
applicationCfc.getData(cfAPPLICATION.SETCLIENTCOOKIES).getBoolean(),
applicationCfc.getData(cfAPPLICATION.SETDOMAINCOOKIES).getBoolean(),
applicationCfc.getData(cfAPPLICATION.SESSIONMANAGEMENT).getBoolean(),
applicationCfc.getData(cfAPPLICATION.CLIENTMANAGEMENT).getBoolean(),
applicationCfc.getData(cfAPPLICATION.CLIENTSTORAGE).getString(),
applicationCfc.getData(cfAPPLICATION.LOGINSTORAGE).getString(),
applicationCfc.getComponentPath(),
scriptProtect,
applicationCfc.getData(cfAPPLICATION.SECUREJSON).getBoolean(),
applicationCfc.getData(cfAPPLICATION.SECUREJSONPREFIX).getString(),
datasource, sessionstorage, applicationCfc );
}
public static void onApplicationEnd(cfApplicationData appData) {
String applicationCfcPath = appData.getApplicationCfcPath();
if (applicationCfcPath == null)
return;
cfSession session = new cfSession(new dummyServletRequest(appData.getWebroot()), new dummyServletResponse(), cfEngine.thisServletContext);
cfComponentData applicationCfc = null;
try {
cfFile applicationFile = session.getRealFile(applicationCfcPath);
applicationFile.setComponentName("Application");
applicationCfc = new cfComponentData(session, applicationFile, false); // false = don't allow abstract
List<cfApplicationData> args = new ArrayList<cfApplicationData>();
args.add(appData); // ApplicationScope
cfcMethodData methodData = new cfcMethodData(session, ON_APPLICATION_END, args);
applicationCfc.invokeApplicationFunction(session, methodData);
} catch (cfmAbortException ignore) {
// do nothing, we're finished anyway (catch here so it's not caught as Throwable below)
} catch (cfmRunTimeException e) {
try {
session.invokeOnError(applicationCfc, e, ON_APPLICATION_END);
} catch (cfmRunTimeException ie) {
cfEngine.log("RunTimeError in onApplicationEnd: " + applicationCfcPath);
ie.handleException(session);
}
} catch (Throwable t) {
com.nary.Debug.printStackTrace(t);
new cfmRunTimeException(session, t).handleException(session);
} finally {
// Make sure per request connections are closed (bug #3174)
session.sessionEnd();
}
}
public void loadApplication(cfTag parentTag, cfSession _Session) throws cfmRunTimeException {
// This method is invoked from the CFAPPLICATION tag and will initialise and setup all the application data storage
String appName = parentTag.getDynamic(_Session, "NAME").getString();
// Get the application data instance
cfApplicationData appData = getAppData(_Session, appName);
String scriptProtect = null;
if (parentTag.containsAttribute(cfAPPLICATION.SCRIPTPROTECT)) {
scriptProtect = parentTag.getDynamic(_Session, cfAPPLICATION.SCRIPTPROTECT).getString();
}
String datasource = null;
if ( parentTag.containsAttribute(cfAPPLICATION.DATASOURCE) )
datasource = parentTag.getDynamic(_Session, cfAPPLICATION.DATASOURCE).getString();
String sessionstorage = null;
if ( parentTag.containsAttribute(cfAPPLICATION.SESSIONSTORAGE) )
sessionstorage = parentTag.getDynamic(_Session, cfAPPLICATION.SESSIONSTORAGE).getString();
if ( parentTag.containsAttribute(cfAPPLICATION.CUSTOMTAGPATHS) )
_Session.setDataBin(cfAPPLICATION.CUSTOMTAGPATHS, parentTag.getDynamic(_Session, cfAPPLICATION.CUSTOMTAGPATHS).getString() );
// Setup the properties of this application
appData.onRequestStart(_Session, bJ2EESessionManagement,
(long) (parentTag.getDynamic(_Session, cfAPPLICATION.APPLICATIONTIMEOUT).getDouble() * DateUtils.MILLIS_PER_DAY),
(long) (parentTag.getDynamic(_Session, cfAPPLICATION.SESSIONTIMEOUT).getDouble() * DateUtils.MILLIS_PER_DAY),
parentTag.getDynamic(_Session, cfAPPLICATION.SETCLIENTCOOKIES).getBoolean(),
parentTag.getDynamic(_Session, cfAPPLICATION.SETDOMAINCOOKIES).getBoolean(),
parentTag.getDynamic(_Session, cfAPPLICATION.SESSIONMANAGEMENT).getBoolean(),
parentTag.getDynamic(_Session, cfAPPLICATION.CLIENTMANAGEMENT).getBoolean(),
parentTag.getDynamic(_Session, cfAPPLICATION.CLIENTSTORAGE).getString(),
parentTag.getDynamic(_Session, cfAPPLICATION.LOGINSTORAGE).getString(),
null,
scriptProtect,
parentTag.getDynamic(_Session, cfAPPLICATION.SECUREJSON).getBoolean(),
parentTag.getDynamic(_Session, cfAPPLICATION.SECUREJSONPREFIX).getString(),
datasource, sessionstorage, null );
}
public cfApplicationData getAppData(cfSession _Session, String appName) {
if (bJ2EESessionManagement && appName.equals(cfAPPLICATION.UNNAMED_APPNAME))
return new cfJ2EEApplicationData(_Session);
cfApplicationData appData = getAppData(appName);
if (appData == null)
appData = createAppData(_Session, appName);
return appData;
}
//Try to get the application data in its original unwrapped form.
private cfApplicationData getAppData(String appName) {
cfApplicationData appData = null;
Object obj = cfEngine.thisServletContext.getAttribute(appName);
if (obj instanceof cfApplicationData)
appData = (cfApplicationData) obj;
return appData;
}
private synchronized cfApplicationData createAppData(cfSession session, String appName) {
cfApplicationData appData = getAppData(appName);
if (appData == null) {
appData = new cfApplicationData(session, appName);
appData.setApplicationStart(true);
// Place the application data in the application scope in its wrapped form
cfEngine.thisServletContext.setAttribute(appName, appData);
cfEngine.log("cfApplicationManager.onApplicationStart: " + (appName.equals(cfAPPLICATION.UNNAMED_APPNAME) ? "(unnamed application)" : appName));
}
return appData;
}
}