/**
Copyright (C) 2012 Delcyon, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.delcyon.capo;
import java.io.PrintStream;
import java.net.URL;
import java.security.KeyStore;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import org.scannotation.AnnotationDB;
import org.scannotation.ClasspathUrlFinder;
import org.tanukisoftware.wrapper.WrapperListener;
import org.tanukisoftware.wrapper.WrapperManager;
import org.w3c.dom.Document;
import com.delcyon.capo.client.CapoClient;
import com.delcyon.capo.resourcemanager.CapoDataManager;
import com.delcyon.capo.server.CapoServer;
import com.delcyon.capo.tasks.TaskManagerThread;
import com.delcyon.capo.util.LeveledConsoleHandler;
import com.delcyon.capo.util.LeveledConsoleHandler.Output;
import com.delcyon.capo.util.LogPrefixFormatter;
import com.delcyon.capo.xml.CapoXPathFunctionResolver;
import com.delcyon.capo.xml.XPath;
import eu.medsea.mimeutil.MimeUtil;
/**
* @author jeremiah
* This class pieces together the common functionality between the client and the server programs.
* It takes care of logging, annotation processing, system properties, and always contains a reference to the running application so that decisions can be made based on client or server contexts.
*/
public abstract class CapoApplication extends ContextThread implements WrapperListener
{
/**
* The order of this enum is important, as we use the ordinal value of this list internally.
* @author jeremiah
*
*/
public enum ApplicationState
{
NONE,
INITIALIZING,
INITIALIZED,
STARTING,
READY,
STOPPING,
STOPPED
}
public enum DefaultDocument
{
default_capo,
default_response,
default_resourceMonitors,
default_request,
config
}
public enum Location
{
CLIENT,SERVER,BOTH
}
public static final String RESOURCE_NAMESPACE_URI = "http://www.delcyon.com/capo/resource";
public static final String CAPO_NAMESPACE_URI = "http://www.delcyon.com/capo";
public static final String XSD11_SCHEMA_LANGUAGE = "http://www.w3.org/XML/XMLSchema/v1.1";
protected static CapoApplication capoApplication;
public static Level LOGGING_LEVEL = Level.INFO;
public static Logger logger = null;
private static LeveledConsoleHandler leveledConsoleHandler;
private static FileHandler fileHandler;
private static String logFileName = null;
private Transformer transformer;
private Map<String, Set<String>> annotaionMap;
private CopyOnWriteArrayList<Exception> exceptionList;
private SSLSocketFactory sslSocketFactory;
private HashMap<String, String> applicationVariableHashMap = new HashMap<String, String>();
private ConcurrentHashMap<String, Object> globalObjectStorage = new ConcurrentHashMap<String, Object>();
private KeyStore keyStore = null;
public static final String SERVER_NAMESPACE_URI = "http://www.delcyon.com/capo-server";
public static final String CLIENT_NAMESPACE_URI = "http://www.delcyon.com/capo-client";
private CapoDataManager dataManager = null;
private TaskManagerThread taskManagerThread = null;
private Configuration configuration = null;
private DocumentBuilderFactory documentBuilderFactory;
private volatile ApplicationState applicationState = ApplicationState.NONE;
public static void main(String[] args)
{
String clientAsServiceArgument = "--"+CapoClient.Preferences.CLIENT_AS_SERVICE;
try
{
boolean clientAsServiceAlreadySet = false;
for (String arg : args)
{
if(arg.equals(clientAsServiceArgument))
{
clientAsServiceAlreadySet = true;
break;
}
}
List<String> argsVector = new Vector<String>();
Collections.addAll(argsVector, args);
if(args.length == 0 || args[0].matches("(-server|-client)") == false)
{
System.out.println("defaulting to client. You may specify (-server|-client) as the first argument, to control this.");
if(clientAsServiceAlreadySet == false)
{
Collections.addAll(argsVector, new String[]{clientAsServiceArgument,"false"});
}
CapoClient.main(argsVector.toArray(args));
}
else
{
argsVector = argsVector.subList(1, argsVector.size());
if(args[0].equals("-server"))
{
CapoServer.main(argsVector.toArray(args));
}
else
{
if(clientAsServiceAlreadySet == false)
{
Collections.addAll(argsVector, new String[]{clientAsServiceArgument,"false"});
}
CapoClient capoClient = new CapoClient();
capoClient.start(argsVector.toArray(args));
}
}
} catch (Exception exception)
{
exception.printStackTrace();
}
}
public CapoApplication() throws Exception
{
setApplication(this);
System.setProperty("java.util.prefs.syncInterval", "2000000");
System.setProperty("javax.xml.xpath.XPathFactory", "net.sf.saxon.xpath.XPathFactoryImpl");
System.setProperty("javax.xml.transform.TransformerFactory", "net.sf.saxon.TransformerFactoryImpl");
System.setProperty("javax.xml.parsers.DocumentBuilderFactory", "com.delcyon.capo.xml.cdom.CDocumentBuilderFactory");
//System.setProperty("javax.net.debug","all");
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
System.setProperty("sun.security.ssl.allowLegacyHelloMessages", "true");
System.setProperty("hsqldb.reconfig_logging", "false");
if (logger == null)
{
logger = Logger.getLogger(this.getClass().getName());
logger.setLevel(LOGGING_LEVEL);
if (logger.getHandlers().length == 0) //this is here for testing so that we don't just keep adding handlers everytime we start something.
{
if (leveledConsoleHandler == null)
{
leveledConsoleHandler = new LeveledConsoleHandler();
leveledConsoleHandler.setLevel(LOGGING_LEVEL);
leveledConsoleHandler.setOutputForLevel(Output.STDERR, Level.FINER);
leveledConsoleHandler.setFormatter(new LogPrefixFormatter(getApplication().getApplicationDirectoryName().toUpperCase()+": "));
logger.setUseParentHandlers(false);
logger.addHandler(leveledConsoleHandler);
}
if (fileHandler == null)
{
logFileName = getApplication().getApplicationDirectoryName().replaceAll(" ", "_").toLowerCase() + ".log";
logger.log(Level.FINE, "Opening LogElement File:" + logFileName);
fileHandler = new FileHandler(logFileName);
fileHandler.setLevel(LOGGING_LEVEL);
logger.addHandler(fileHandler);
}
}
}
PropertyResourceBundle versionResourceBundle = new PropertyResourceBundle(ClassLoader.getSystemResource("version.properties").openStream());
String version = versionResourceBundle.getString("version");
String versionDate = versionResourceBundle.getString("version.date");
logger.log(Level.INFO, "Starting Capo "+getApplication().getApplicationDirectoryName()+" version: "+version+" compiled on: "+versionDate);
//setup Mime Type Processing
MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector");
MimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.ExtensionMimeDetector");
documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
//setup xml output
TransformerFactory tFactory = TransformerFactory.newInstance();
transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//find annotations
URL[] urls = ClasspathUrlFinder.findClassPaths();
AnnotationDB annotationDB = new AnnotationDB();
annotationDB.scanArchives(urls);
annotaionMap = annotationDB.getAnnotationIndex();
//setup XPathFunction Resolvers
XPath.setXPathFunctionResolver(new CapoXPathFunctionResolver());
}
public ApplicationState getApplicationState()
{
return applicationState;
}
protected void setApplicationState(ApplicationState applicationState)
{
logger.log(Level.INFO, applicationState.toString());
this.applicationState = applicationState;
}
protected abstract void startup(String[] programArgs) throws Exception;
protected abstract void init(String[] programArgs) throws Exception;
public abstract Integer start(String[] programArgs);
public int stop( int exitCode )
{
try
{
TaskManagerThread taskManagerThread = getTaskManagerThread();
//May have never been started, due to initial update
if(taskManagerThread != null)
{
taskManagerThread.interrupt();
}
logger.log(Level.INFO, "Requesting Application Shutdown");
shutdown();
logger.log(Level.INFO, "Application Shutdown");
setApplication(null);
}
catch (Exception exception)
{
exception.printStackTrace();
return 1;
}
return 0;
}
public void controlEvent( int event )
{
if (( event == WrapperManager.WRAPPER_CTRL_LOGOFF_EVENT ) && ( WrapperManager.isLaunchedAsService() || WrapperManager.isIgnoreUserLogoffs() ) )
{
// Ignore
}
else
{
WrapperManager.stop( 0 );
// Will not get here.
}
}
public void restart()
{
WrapperManager.restart();
}
public abstract void shutdown() throws Exception;
public static Configuration getConfiguration()
{
return getApplication().configuration;
}
public static void setConfiguration(Configuration configuration)
{
getApplication().configuration = configuration;
}
public static CapoApplication getApplication()
{
return capoApplication;
}
/**
* This is mostly here for the sake of completion and testing. Other than testing there's probably no good reason to use this.
* @param application
*/
protected static void setApplication(CapoApplication application)
{
if (application == null && CapoApplication.logger.isLoggable(Level.FINER))
{
Thread.dumpStack();
}
CapoApplication.capoApplication = application;
}
public static CapoDataManager getDataManager()
{
return getApplication().dataManager;
}
public static boolean isServer()
{
return getApplication() instanceof CapoServer;
}
public static void setDataManager(CapoDataManager dataManager)
{
getApplication().dataManager = dataManager;
}
public static void setTaskManagerThread(TaskManagerThread taskManagerThread)
{
getApplication().taskManagerThread = taskManagerThread;
}
public static TaskManagerThread getTaskManagerThread()
{
return getApplication().taskManagerThread;
}
public static DocumentBuilder getDocumentBuilder() throws Exception
{
return getApplication().documentBuilderFactory.newDocumentBuilder();
}
public static Document getDefaultDocument(String defaultDocumentName) throws Exception
{
return getDocumentBuilder().parse(ClassLoader.getSystemResource("defaults/"+defaultDocumentName).openStream());
}
public static Object getGlobalObject(String key)
{
return getApplication().globalObjectStorage.get(key);
}
public static void setGlobalObject(String key, Object object)
{
getApplication().globalObjectStorage.put(key,object);
}
public static Object removeGlobalObject(String key)
{
return getApplication().globalObjectStorage.remove(key);
}
public abstract String getApplicationDirectoryName();
public static Map<String, Set<String>> getAnnotationMap()
{
if (getApplication() != null)
{
return getApplication().annotaionMap;
}
else
{
return null;
}
}
public static void setVariable(String varName, String value)
{
logger.log(Level.INFO, "Set application var "+varName+" to "+value);
getApplication().applicationVariableHashMap.put(varName, value);
}
public static String getVariableValue(String varName)
{
return getApplication().applicationVariableHashMap.get(varName);
}
public static void dumpVars(PrintStream printStream)
{
printStream.println("\n====================APPLICATION VARS=====================");
if (getApplication() != null)
{
printStream.println(getApplication().applicationVariableHashMap);
}
printStream.println("====================END APPLICATION VARS=====================\n");
}
public static String removeVariableValue(String varName)
{
return getApplication().applicationVariableHashMap.remove(varName);
}
public static SSLSocketFactory getSslSocketFactory()
{
return getApplication().sslSocketFactory;
}
public void setSslSocketFactory(SSLSocketFactory sslSocketFactory)
{
this.sslSocketFactory = sslSocketFactory;
}
public void setKeyStore(KeyStore keyStore)
{
this.keyStore = keyStore;
}
public static KeyStore getKeyStore()
{
return getApplication().keyStore;
}
public static byte[] getCeritifcate() throws Exception
{
if (getApplication().keyStore != null)
{
return getApplication().keyStore.getCertificate("capo.server.cert").getEncoded();
}
else
{
return null;
}
}
public void setExceptionList(CopyOnWriteArrayList<Exception> exceptionList)
{
this.exceptionList = exceptionList;
}
public CopyOnWriteArrayList<Exception> getExceptionList()
{
return exceptionList;
}
}