/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA 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.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA 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 OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.webgate.api;
import java.io.File;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.Configuration;
import net.sf.ehcache.config.DiskStoreConfiguration;
import org.apache.log4j.Logger;
import de.innovationgate.webgate.api.auth.AuthModuleFactory;
import de.innovationgate.webgate.api.auth.DefaultAuthModuleFactory;
import de.innovationgate.webgate.api.mail.WGMailService;
import de.innovationgate.webgate.api.servers.WGDatabaseServer;
import de.innovationgate.webgate.api.workflow.WGDefaultWorkflowEngine;
import de.innovationgate.webgate.api.workflow.WGWorkflowEngine;
import de.innovationgate.wga.modules.ModuleRegistry;
/**
* Entry class to use the WGAPI in any program. Via this singleton class all databases are initially opened.
*/
public class WGFactory {
/**
* System property to turn off the usage of an event thread. To do that set the system property: de.innovationgate.wgapi.eventthread := false
*/
public static final String SYSPROPERTY_EVENTTHREAD = "de.innovationgate.wgapi.eventthread";
private static boolean _useEventThread = true;
private static File tempDir = new File(System.getProperty("java.io.tmpdir"));
private static WGFactory _instance = new WGFactory();
private static Logger _logger = Logger.getLogger("wga.api");
private static ClassLoader _implementationLoader = Thread.currentThread().getContextClassLoader();
private static AuthModuleFactory _authModuleFactory = new DefaultAuthModuleFactory();
private static ModuleRegistry _moduleRegistry = new ModuleRegistry();
private static WGMailService _mailService = null;
private static MimetypeDeterminationService _mimetypeDeterminationService = new DefaultMimetypeDeterminationService();
private static Class<? extends WGWorkflowEngine> _defaultWorkflowEngine = WGDefaultWorkflowEngine.class;
/**
* Returns the module registry to use by the WGAPI
*/
public static ModuleRegistry getModuleRegistry() {
return _moduleRegistry;
}
/**
* Sets the module registry to use by the WGAPI
*/
public static void setModuleRegistry(ModuleRegistry moduleRegistry) {
_moduleRegistry = moduleRegistry;
}
/**
* A persister for database revisions of type {@link java.util.Date}
*/
public static class DateRevisionPersister implements WGDatabaseRevisionSerializer {
private static final DateFormat DATEFORMAT = new SimpleDateFormat("yyyyMMdd-HHmmss");
public Comparable fromPersistentForm(String revisionStr) throws ParseException {
return DATEFORMAT.parse(revisionStr);
}
public String toPersistentForm(Comparable revision) {
return DATEFORMAT.format((Date) revision);
}
}
/**
* A persister for database revisions of type {@link Long}
*/
public static class LongIntegerRevisionPersister implements WGDatabaseRevisionSerializer {
public Comparable fromPersistentForm(String revisionStr) {
return Long.valueOf(revisionStr);
}
public String toPersistentForm(Comparable revision) {
return ((Long) revision).toString();
}
}
private static Map<Class,WGDatabaseRevisionSerializer> _revisionPersisters = new HashMap<Class, WGDatabaseRevisionSerializer>();
static {
_revisionPersisters.put(Date.class, new DateRevisionPersister());
_revisionPersisters.put(Long.class, new LongIntegerRevisionPersister());
}
/**
* Returns a class that is able to serialize revision information of the given class.
* @param revClass The java class in which revisions are served
* @throws WGAPIException
*/
public static WGDatabaseRevisionSerializer getRevisionSerializer(Class revClass) throws WGAPIException {
if (Date.class.isAssignableFrom(revClass)) {
revClass = Date.class;
}
WGDatabaseRevisionSerializer persister = _revisionPersisters.get(revClass);
if (persister != null) {
return persister;
}
else {
throw new WGIllegalStateException("There is no persister for revision class '" + revClass.getName() + "'");
}
}
private int _cacheCapacity = 10000;
private WGAPICache _cache = null;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgdocument;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgdesigndocument;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgcontent;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgstructentry;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgarea;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgcontenttype;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgcssjsmodule;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgfilecontainer;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wglanguage;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wgtmlmodule;
/**
* map for metadata-framework of wgapi
* NOTE: ignore IDE Warning: "This fields are unused" --> accessed via reflection in getMetaInfo()
*/
private Map _metainfos_wguserprofile;
static {
String eventThreadStr = (String) System.getProperty(SYSPROPERTY_EVENTTHREAD);
if (eventThreadStr != null) {
_useEventThread = Boolean.valueOf(eventThreadStr).booleanValue();
getLogger().info("WGAPI: Event thread is turned " + (_useEventThread ? "ON" : "OFF"));
}
}
private List _dbs = new ArrayList();
private WGEventThread _eventThread = null;
private CacheManager _cacheManager = null;
private int _cacheIndex = 0;
private static ThreadLocal _isEventThread = new ThreadLocal();
/**
* Returns true, if the current thread is the WGAPI event thread
*/
public static boolean isEventThread() {
Boolean is = (Boolean) _isEventThread.get();
if (is != null) {
return is.booleanValue();
}
else {
return false;
}
}
private WGFactory() {
// initialize metadata framework
_metainfos_wgdocument = Collections.unmodifiableMap(gatherMetaInfos(WGDocument.class));
_metainfos_wgdesigndocument = Collections.unmodifiableMap(gatherMetaInfos(WGDesignDocument.class));
_metainfos_wgcontent = Collections.unmodifiableMap(gatherMetaInfos(WGContent.class));
_metainfos_wgstructentry = Collections.unmodifiableMap(gatherMetaInfos(WGStructEntry.class));
_metainfos_wgarea = Collections.unmodifiableMap(gatherMetaInfos(WGArea.class));
_metainfos_wgcontenttype = Collections.unmodifiableMap(gatherMetaInfos(WGContentType.class));
_metainfos_wgcssjsmodule = Collections.unmodifiableMap(gatherMetaInfos(WGCSSJSModule.class));
_metainfos_wgfilecontainer = Collections.unmodifiableMap(gatherMetaInfos(WGFileContainer.class));
_metainfos_wglanguage = Collections.unmodifiableMap(gatherMetaInfos(WGLanguage.class));
_metainfos_wgtmlmodule = Collections.unmodifiableMap(gatherMetaInfos(WGTMLModule.class));
_metainfos_wguserprofile = Collections.unmodifiableMap(gatherMetaInfos(WGUserProfile.class));
}
/**
* retrieves all metainfos of the given class
* @param c
* @return
*/
private HashMap gatherMetaInfos(Class c) {
HashMap map = new HashMap();
Field[] fields = c.getFields();
for (int i=0; i < fields.length; i++) {
Field field = fields[i];
if (field.getName().startsWith("META_")) {
try {
String metaName = (String) field.get(null);
String metaInfoFieldName = "METAINFO_" + field.getName().substring(5,field.getName().length());
Field metaInfoField = c.getField(metaInfoFieldName);
MetaInfo metaInfo = (MetaInfo) metaInfoField.get(null);
// check info
if (!metaName.equals(metaInfo.getName())) {
throw new RuntimeException("Unable to initialize metadata-framework. MetaInfo for metafield '" + field.getName() + "' of class ' " + c.getName() + "' is invalid. Name mismatch!.");
}
metaInfo.setDefiningClass(c);
map.put(metaName, metaInfo);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to initialize metadata-framework." , e);
}
catch (SecurityException e) {
throw new RuntimeException("Unable to initialize metadata-framework." , e);
}
catch (NoSuchFieldException e) {
throw new RuntimeException("Unable to initialize metadata-framework. MetaInfo for metafield '" + field.getName() +"' of class '" + c.getName() + "' not found.", e);
}
}
}
return map;
}
/**
* @return returns the MetaInfo for given key of the given class
* @throws WGSystemException if the metaDataFramework cannot be accessed
*/
public MetaInfo getMetaInfo(String key, Class c) throws WGSystemException {
Map metaInfos = getMetaInfos(c);
return (MetaInfo) metaInfos.get(key);
}
/**
* returns the MetaInfos Map for the given class
* @param c Class
* @return map - MetaInfos mapped by metaName
* @throws WGSystemException
*/
public Map<String,MetaInfo> getMetaInfos(Class c) throws WGSystemException {
// Redirect class to query for subclasses
if (c.equals(WGScriptModule.class)) {
c = WGCSSJSModule.class;
}
String metaInfosMapFieldName = "_metainfos_" + c.getSimpleName().toLowerCase();
Map<String,MetaInfo> metaInfos;
try {
Field metaInfoField = getClass().getDeclaredField(metaInfosMapFieldName);
metaInfos = (Map<String,MetaInfo>) metaInfoField.get(this);
}
catch (IllegalArgumentException e) {
throw new WGSystemException("Unable to access metadata-framework.", e);
}
catch (SecurityException e) {
throw new WGSystemException("Unable to access metadata-framework.", e);
}
catch (NoSuchFieldException e) {
throw new WGSystemException("Unable to access metadata-framework.", e);
}
catch (IllegalAccessException e) {
throw new WGSystemException("Unable to access metadata-framework.", e);
}
return metaInfos;
}
/**
* Set if the current thread is the WGA event thread
*/
protected static void setEventThread(boolean value) {
_isEventThread.set(new Boolean(value));
}
/**
* Returns the singleton instance of the factory
*/
public static WGFactory getInstance() {
return _instance;
}
/* (Kein Javadoc)
* @see java.lang.Object#finalize()
*/
public void finalize() {
if (this._eventThread != null) {
this._eventThread.stop();
this._eventThread = null;
}
}
/**
* Opens a WGA database initially.
* @param server The database server that this database resides on
* @param strType The type of the database, i.e. the database implementation class to use
* @param strPath The path of the database. Varies by implementation.
* @param strUserName The user name to use as master login.
* @param strUserPwd The password for the master login.
* @param options Additional creation options.
* @param prepareOnly Lets the database only be prepared (test connectivity) and connected on first session
* @throws WGAPIException
*/
public synchronized WGDatabase openDatabase(WGDatabaseServer server, String strType, String strPath, String strUserName, String strUserPwd, Map options, boolean prepareOnly) throws WGAPIException {
prepareForDBUsage();
WGDatabase db = new WGDatabase(server, strType, strPath, strUserName, strUserPwd, options, prepareOnly);
if (db.isReady() == false) {
return null;
}
List newList = new ArrayList(_dbs);
newList.add(db);
_dbs = newList;
if (this._eventThread == null && _useEventThread) {
this._eventThread = new WGEventThread();
this._eventThread.start();
}
return db;
}
private synchronized void prepareForDBUsage() throws WGAPIException {
if (!isCacheInitialized()) {
initializeCache();
}
}
private int increaseCacheIndex() {
_cacheIndex ++;
return _cacheIndex;
}
/**
* Opens a WGA database initially, ready for work. A database master session will already be opened.
* @param server The database server that this database resides on
* @param strType The type of the database, i.e. the database implementation class to use
* @param strPath The path of the database. Varies by implementation.
* @param strUserName The user name to use as master login.
* @param strUserPwd The password for the master login.
* @param options Additional creation options.
* @throws WGAPIException
*/
public synchronized WGDatabase openDatabase(WGDatabaseServer server, String strType, String strPath, String strUserName, String strUserPwd, Map options) throws WGAPIException {
return openDatabase(server, strType, strPath, strUserName, strUserPwd, options, false);
}
/**
* Opens a WGA database initially, but only prepares it for work. The connectivity is tested but the connection terminated afterwards.
* No database session is opened, when the database object is returned.
* The database core remains unconnected to save resources. It will get connected on the first session that is opened.
* @param server The database server that this database resides on
* @param strType The type of the database, i.e. the database implementation class to use
* @param strPath The path of the database. Varies by implementation.
* @param strUserName The user name to use as master login.
* @param strUserPwd The password for the master login.
* @param options Additional creation options.
* @throws WGAPIException
*/
public synchronized WGDatabase prepareDatabase(WGDatabaseServer server, String strType, String strPath, String strUserName, String strUserPwd, Map options) throws WGAPIException {
return openDatabase(server, strType, strPath, strUserName, strUserPwd, options, true);
}
/**
* Close all open sessions on all databases.
* This method performs additionally cleanup operations and should be called each time working with the WGAPI has finished for the current session.
*/
public void closeSessions() {
WGDatabase db;
Map dbsByType = new HashMap();
// Close all open databases
for (int idx = 0; idx < _dbs.size(); idx++) {
db = (WGDatabase) _dbs.get(idx);
if (db.isSessionOpen()) {
try {
db.closeSession();
}
catch (WGAPIException e) {
WGFactory.getLogger().error("Exception closing WGAPI session on database '" + db.getDbReference() + "'", e);
}
dbsByType.put(db.getCore().getClass(), db);
}
}
// Perform cleanup operations on database types that were open
Iterator dbs = dbsByType.values().iterator();
while (dbs.hasNext()) {
db = (WGDatabase) dbs.next();
db.cleanup();
}
}
/**
* Opens sessions on all not yet opened databases with a given login.
* @param user
* @param password
* @throws WGAPIException
*/
public void openSessions(String user, String password) throws WGAPIException {
for (int i=0; i < _dbs.size(); i++) {
((WGDatabase) _dbs.get(i)).openSession(user, password);
}
}
/**
* Opens sessions on all not yet opened databases with master session information.
* @throws WGAPIException
*/
public void openSessions() throws WGAPIException {
this.openSessions(null, null);
}
/**
* Returns a list of all opened database objects.
* This includes all databases with closed sessions, but no databases that have been permanently closed via WGDatabase.close().
*/
public List getOpenedDatabases() {
return this._dbs;
}
/**
* Permanently close all opened databases.
*/
public void closeAll() {
closeSessions();
Iterator dbList = this._dbs.iterator();
while (dbList.hasNext()) {
try {
((WGDatabase) dbList.next()).close();
}
catch (WGAPIException e) {
getLogger().error("Unable to close db.", e);
}
}
}
/**
* Shutdown all databases and the factory
*/
public void shutdown() {
// Stop the event thread
if (this._eventThread != null) {
this._eventThread.stop();
this._eventThread = null;
}
closeAll();
this._dbs.clear();
// Clear cache of auth module factory
getAuthModuleFactory().clearCache();
// Shutdown the cache manager
if (_cacheManager != null) {
_cacheManager.shutdown();
_cacheManager = null;
}
_cache = null;
}
/**
* Returns the logger object to be used by all objects inside the WGAPI
* @return Logger
*/
public static Logger getLogger() {
return _logger;
}
protected synchronized void removeDatabase(WGDatabase db) {
List newList = new ArrayList(_dbs);
newList.remove(db);
_dbs = newList;
}
/**
* Returns the class loader to load WGAPI implementations
*/
public static ClassLoader getImplementationLoader() {
return _implementationLoader;
}
/**
* Sets a class loader that is used to load WGAPI implementation classes
*/
public static void setImplementationLoader(ClassLoader loader) {
_implementationLoader = loader;
}
/**
* Returns the directory for temporary files used by the WGAPI.
* Defaults to the directory unter system property java.io.tmpdir.
*/
public static File getTempDir() {
return tempDir;
}
/**
* Sets the directory for temporary files used by the WGAPI. WGA must have full access rights for this directory.
*/
public static void setTempDir(File tempDir) {
if (!tempDir.exists() || !tempDir.isDirectory()) {
return;
}
WGFactory.tempDir = tempDir;
}
/**
* Returns the Factory for Authentication Modules used by databases of this factory.
*/
public static AuthModuleFactory getAuthModuleFactory() {
return _authModuleFactory;
}
/**
* Sets the Factory for Authentication Modules used by databases of this factory
*/
public static void setAuthModuleFactory(AuthModuleFactory authModuleFactory) {
_authModuleFactory = authModuleFactory;
}
/**
* Returns the EHCache Cache Manager used by this WGFactory and its databases
* @throws CacheException
*/
public synchronized CacheManager getCacheManager() throws CacheException {
if (_cacheManager == null) {
Configuration config = new Configuration();
DiskStoreConfiguration diskConfig = new DiskStoreConfiguration();
diskConfig.setPath(getTempDir().getPath());
config.addDiskStore(diskConfig);
CacheConfiguration defaultConfig = new CacheConfiguration();
defaultConfig.setMaxElementsInMemory(10000);
defaultConfig.setEternal(false);
defaultConfig.setOverflowToDisk(false);
defaultConfig.setTimeToIdleSeconds(120);
defaultConfig.setTimeToLiveSeconds(120);
defaultConfig.setDiskPersistent(false);
defaultConfig.setDiskExpiryThreadIntervalSeconds(120);
config.addDefaultCache(defaultConfig);
_cacheManager = new CacheManager(config);
}
return _cacheManager;
}
/**
* Returns the EHCache for the global WGAPI data cache
*/
public WGAPICache getCache() {
return _cache;
}
/**
* Returns the capacity of cache entries for the WGAPI data cache
*/
public int getCacheCapacity() {
return _cacheCapacity;
}
/**
* Sets the capacity of cache entries for the WGAPI data cache.
* The WGAPI data cache is globally used by all open databases.
* Each document needs one cache entry for it's complete metadata values.
* Content documents and user profiles use an additional cache entry for their complete item values.
* The default capacity is 10000 entries.
* Setting the capacity is only directly effective when the WGAPI data cache is yet uninitialized (see {@link #isCacheInitialized()}
* You can call {@link #initializeCache()} to build a new cache that uses a newly set capacity.
* @param cacheCapacity
*/
public void setCacheCapacity(int cacheCapacity) {
_cacheCapacity = cacheCapacity;
}
/**
* Recreates and initialized the cache using the most current settings.
* This will remove all elements currently in cache.
* @throws WGAPIException
*/
public synchronized void initializeCache() throws WGAPIException {
String cacheName = "WGFactory-" + increaseCacheIndex();
WGAPICache newCache = null;
/*
String cacheImpl = System.getProperty(SYSPROPERTY_CACHE);
// Instantiate a special cache implementation
if (cacheImpl != null) {
try {
Class clazz = Class.forName(cacheImpl);
newCache = (WGAPICache) clazz.newInstance();
}
catch (Exception e) {
getLogger().error("Error creating WGAPI cache implementation " + cacheImpl, e);
}
}*/
// Use default cache WGAPIGenericCache
if (newCache == null) {
newCache = new WGAPIGenericCache();
}
try {
newCache.init(cacheName, _cacheCapacity);
}
catch (Exception e) {
throw new WGSystemException("Error initializing WGAPI cache", e);
}
if (_cache != null) {
_cache.destroy();
}
_cache = newCache;
}
/**
* Returns if the WGAPI data cache already was initialized.
* This initialisation occurs after the first database was opened
*/
public boolean isCacheInitialized() {
return _cache != null;
}
/**
* Tool method for creating database servers when using WGAPI as library (rather than from inside WGA).
* The created server will have no uid or title.
* @param <T>
* @param serverClass The class of the server to create
* @param options Options for the database server
* @return A newly created database server
* @throws InstantiationException
* @throws IllegalAccessException
* @throws WGAPIException
*/
public <T extends WGDatabaseServer> T createDatabaseServer(Class<T> serverClass, Map<String, String> options) throws InstantiationException, IllegalAccessException, WGAPIException {
T server = serverClass.newInstance();
server.init(null, null, options);
return server;
}
/**
* Returns the default workflow engine to use
*/
public static Class<? extends WGWorkflowEngine> getDefaultWorkflowEngine() {
return _defaultWorkflowEngine;
}
/**
* Sets the default workflow engine to use by the WGAPI
*/
public static void setDefaultWorkflowEngine(Class<? extends WGWorkflowEngine> defaultWorkflowEngine) {
_defaultWorkflowEngine = defaultWorkflowEngine;
}
/**
* Returns the mail service that the WGAPI uses, for example for sending workflow notification mails
*/
public static WGMailService getMailService() {
return _mailService;
}
/**
* Sets the mail service to use in WGAPI
*/
public static void setMailService(WGMailService mailService) {
_mailService = mailService;
}
/**
* Returns a service for determining file mimetypes
*/
public static MimetypeDeterminationService getMimetypeDeterminationService() {
return _mimetypeDeterminationService;
}
/**
* Returns a service for determining file mimetypes
*/
public static void setMimetypeDeterminationService(MimetypeDeterminationService mimetypeDeterminationService) {
_mimetypeDeterminationService = mimetypeDeterminationService;
}
}