Package de.innovationgate.webgate.api

Source Code of de.innovationgate.webgate.api.WGDatabase$SessionStatistic

/*******************************************************************************
* 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.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.WeakHashMap;

import org.apache.commons.collections.SortedBag;
import org.apache.commons.collections.bag.TreeBag;
import org.apache.commons.collections.map.LinkedMap;
import org.apache.commons.collections.map.ListOrderedMap;

import de.innovationgate.utils.CertificateValidationUtils;
import de.innovationgate.utils.NullPlaceHolder;
import de.innovationgate.utils.UserHashMap;
import de.innovationgate.utils.UserHashMapGroup;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGStructEntry.ContentSet;
import de.innovationgate.webgate.api.auth.AnonymousAuthSession;
import de.innovationgate.webgate.api.auth.AuthenticationException;
import de.innovationgate.webgate.api.auth.AuthenticationModule;
import de.innovationgate.webgate.api.auth.AuthenticationSession;
import de.innovationgate.webgate.api.auth.AuthenticationSourceListener;
import de.innovationgate.webgate.api.auth.BackendAuthSession;
import de.innovationgate.webgate.api.auth.CertAuthCapableAuthModule;
import de.innovationgate.webgate.api.auth.LabeledNamesProvider;
import de.innovationgate.webgate.api.auth.MasterLoginAuthSession;
import de.innovationgate.webgate.api.auth.RedirectionAuthModule;
import de.innovationgate.webgate.api.fake.WGFakeDocument;
import de.innovationgate.webgate.api.fake.WGFakeLanguage;
import de.innovationgate.webgate.api.fake.WGFakeUserProfile;
import de.innovationgate.webgate.api.locking.Lock;
import de.innovationgate.webgate.api.locking.LockException;
import de.innovationgate.webgate.api.locking.LockManager;
import de.innovationgate.webgate.api.locking.LockOwner;
import de.innovationgate.webgate.api.locking.Lockable;
import de.innovationgate.webgate.api.locking.ResourceIsLockedException;
import de.innovationgate.webgate.api.schemadef.WGAreaDefinition;
import de.innovationgate.webgate.api.schemadef.WGContentItemDefinition;
import de.innovationgate.webgate.api.schemadef.WGContentTypeDefinition;
import de.innovationgate.webgate.api.schemadef.WGLanguageDefinition;
import de.innovationgate.webgate.api.schemadef.WGMetaFieldDefinition;
import de.innovationgate.webgate.api.schemadef.WGSchemaDefinition;
import de.innovationgate.webgate.api.schemadef.WGSchemaDocumentDefinition;
import de.innovationgate.webgate.api.servers.WGDatabaseServer;
import de.innovationgate.webgate.api.utils.MasterSessionTask;
import de.innovationgate.webgate.api.workflow.WGDefaultWorkflowEngine;
import de.innovationgate.webgate.api.workflow.WGWorkflow;
import de.innovationgate.webgate.api.workflow.WGWorkflowEngine;
import de.innovationgate.webgate.api.workflow.WGWorkflowEvent;
import de.innovationgate.webgate.api.workflow.WGWorkflowEventListener;
import de.innovationgate.wga.common.Constants;
import de.innovationgate.wga.common.beans.csconfig.v1.CSConfig;

/**
* <p>
* Represents a WGA Database. Can be a content database, design database,
* repository database, user profile database or a mixture of it. The Roles of a
* database give info about it's contents. Can be retrieved via getRoles().
* </p>
*
* <h4>Usage patterns:</h4>
*
* <p>
* <b>Simple use: </b>
* <p/>
* <p>
* This is recommended, when there are is only one stream of operation to be
* done and the used databases will not be opened again for a time.
* </p>
*
* <code>
* WGDatabase db = WGFactory.getInstance().openDatabase(dbtype, dbpath, username, password, options);<br/>
* ... db actions ...<br/>
* db.close();<br/>
* </code>
* <p>
* After db.close() the database object must not be used further.
* </p>
*
*
*
* <p>
* <b>Recurring and multithreaded use: </b>
* </p>
* <p>
* This is recommended when the opened databases should be reused at later time
* and/or will be used by multiple threads. This mode introduces the "database
* session", that will open a WGDatabase object for usage and can be closed
* without completely closing the database object and opened again later.
* </p>
* <code>
* // Initial opening - Opens the database and implicitly a session too<br/>
*  WGDatabase db = WGFactory.getInstance().openDatabase(dbtype, dbpath, username, password, options);<br/>
*  ... db actions ...<br/>
*  db.closeSession();<br/>
* <br/>
*  ...<br/>
* <br/>
*  // Recurring opening - maybe in another thread using the same db object<br/>
*  db.openSession(username, password)<br/>
*  ... db actions ...<br/>
*  db.closeSession();<br/>
* <br/>
*  ...<br/>
* <br/>
*  // Final closing at the end of the program - No further WGA processing after that<br/>
*  db.close();<br/>
* </code>
* <p>
* This mode not only allows the WGAPI to keep database connections but also
* allows for caching of field values.
* </p>
*
*/
public class WGDatabase implements Lockable, WGDesignChangeListener, PageHierarchyNode, AuthenticationSourceListener, WGExtensionDataContainer {
   
   
    /** Content store version identifying a database that is no content store at all */
    public static final double CSVERSION_NO_CONTENTSTORE = 0;
   
    /** Content store version identifying a content store used as of WGA version 3.0 */
    public static final double CSVERSION_WGA3 = 3;
   
    /** Content store version identifying a content store used as of WGA version 4.1, including optimized file handling */
    public static final double CSVERSION_WGA4_1 = 4.1;

    /** Content store version identifying a content store used as of WGA version 5 */
    public static final double CSVERSION_WGA5 = 5;

    /**
     * Name of the implicit "authenticated" group holding all users that were able to authenticate themselves
     */
    public static final String AUTHENTICATED_GROUP = "authenticated";
   
    public class DocumentCollectionHierarchy implements PageHierarchyNode {
       
        private int _type;

        public DocumentCollectionHierarchy(int type) {
            _type = type;
        }

        public Class getChildNodeType() {
            switch (_type) {
                case WGDocument.TYPE_AREA:
                    return WGArea.class;
                   
                case WGDocument.TYPE_LANGUAGE:
                    return WGLanguage.class;
                   
                case WGDocument.TYPE_CONTENTTYPE:
                    return WGContentType.class;
                   
                case WGDocument.TYPE_CSSJS:
                    return WGScriptModule.class;
                   
                case WGDocument.TYPE_TML:
                    return WGTMLModule.class;
                   
                case WGDocument.TYPE_FILECONTAINER:
                    return WGFileContainer.class;
                   
                default:
                    throw new IllegalStateException("Unknown document type " + _type);
                   
            }
        }

        public List<PageHierarchyNode> getChildNodes() throws WGAPIException {
           return getDesignObjects(_type);
        }

        public String getNodeKey() throws WGAPIException {
            return WGDocument.doctypeNumberToName(_type);
        }

        public String getNodeTitle(String language) throws WGAPIException {
           
            switch (_type) {
               
                case WGDocument.TYPE_AREA: return "Areas";
               
                case WGDocument.TYPE_CONTENTTYPE: return "Content types";
               
                case WGDocument.TYPE_LANGUAGE: return "Languages";
               
                case WGDocument.TYPE_FILECONTAINER: return "File containers";
               
                case WGDocument.TYPE_TML: return "WebTML modules";
               
                case WGDocument.TYPE_CSSJS: return "Scripts";
               
               
            }
           
            return WGDocument.doctypeNumberToName(_type);
        }

        public PageHierarchyNode getParentNode() throws WGAPIException {
            return WGDatabase.this;
        }
       
    }
   
    public class AllDocumentsHierarchy {
       
        private ListOrderedMap _docCollections = new ListOrderedMap();
       
        public AllDocumentsHierarchy() {
            _docCollections.put(WGDocument.TYPE_AREA, new DocumentCollectionHierarchy(WGDocument.TYPE_AREA));
            _docCollections.put(WGDocument.TYPE_CONTENTTYPE, new DocumentCollectionHierarchy(WGDocument.TYPE_CONTENTTYPE));
            _docCollections.put(WGDocument.TYPE_LANGUAGE, new DocumentCollectionHierarchy(WGDocument.TYPE_LANGUAGE));
            _docCollections.put(WGDocument.TYPE_CSSJS, new DocumentCollectionHierarchy(WGDocument.TYPE_CSSJS));
            _docCollections.put(WGDocument.TYPE_TML, new DocumentCollectionHierarchy(WGDocument.TYPE_TML));
            _docCollections.put(WGDocument.TYPE_FILECONTAINER, new DocumentCollectionHierarchy(WGDocument.TYPE_FILECONTAINER));
        }
       
        public DocumentCollectionHierarchy getCollectionForType(int type) {
            return (DocumentCollectionHierarchy) _docCollections.get(new Integer(type));
        }

        @SuppressWarnings("unchecked")
        public List<PageHierarchyNode> getChildNodes() throws WGAPIException {
            return _docCollections.valueList();
        }

    }
   
private AllDocumentsHierarchy _allDocumentsHierarchy = new AllDocumentsHierarchy();
    /**
     * An interface defining an action that can be execute when the current
     * database is connected.
     */
    public interface ConnectAction {
        public void run(WGDatabase db) throws Exception;
    }

    /**
     * Defines a behaviour for WGDatabases how they should respond to requests
     * for nonexistent items
     */
    public class NoItemBehaviour extends Object {

        private String behaviour;

        private Object forGetItemValue;

        private String forGetItemText;

        private List forGetItemValueList;

        private Object forTMLItem;

        private List forTMLItemList;

        private Object forTMLFormField;

        private List forTMLFormFieldList;

        private Object forTMLFormEmptyField;

        private List forTMLFormEmptyFieldList;

        /**
         * Default contructor defining the "straight" behaviour which is default
         * since WGA 4
         */
        public NoItemBehaviour() {
            straight();
        }

        /**
         * Sets the behaviour complicance to the given behaviour string. Uses
         * Constants {@link CSConfig#VERSIONCOMPLIANCE_WGA3} or
         * {@link CSConfig#VERSIONCOMPLIANCE_WGA4}
         */
        public void compliantTo(String compliance) {

            if (!compliance.equals(behaviour)) {

                //As we now have many version compliance settings without implication we should not log each non-default setting
                //WGFactory.getLogger().info("Changing WGA behaviour compliance for database " + getDbReference() + " to '" + compliance + "'");
                if (compliance.equals(CSConfig.VERSIONCOMPLIANCE_WGA3)) {
                    compatibleForDBImplementation(getCore());
                }
                else {
                    straight();
                }
            }

        }

        public void straight() {

            behaviour = CSConfig.VERSIONCOMPLIANCE_WGA51;
            forGetItemValue = null;
            forGetItemText = null;
            forGetItemValueList = new ArrayList();
            forTMLItem = null;
            forTMLItemList = new ArrayList();
            forTMLFormField = null;
            forTMLFormFieldList = new ArrayList();
            forTMLFormEmptyField = null;
            forTMLFormEmptyFieldList = new ArrayList();
        }

        /**
         * Assures WGA 3.3 compatibility for the given database type
         *
         * @param core
         */
        public void compatibleForDBImplementation(WGDatabaseCore core) {

            behaviour = CSConfig.VERSIONCOMPLIANCE_WGA3;

            List listWithEmptyString = new ArrayList();
            listWithEmptyString.add("");

            // Implementation independent in WGA 3.3
            forTMLItem = "";
            forTMLFormField = null;
            forTMLFormFieldList = null;
            forTMLFormEmptyField = "";
            // B00004716
            forTMLFormEmptyFieldList = new ArrayList();

            // Implementation dependent in WGA 3.3
            if (core.getTypeName().startsWith("domino/")) {
                forGetItemValue = "";
                forGetItemValueList = listWithEmptyString;
                forGetItemText = "";
                forTMLItemList = listWithEmptyString;
            }
            else if (core.getTypeName().startsWith("jdbc/")) {
                forGetItemValue = new ArrayList();
                forGetItemValueList = new ArrayList();
                forGetItemText = null;
                forTMLItemList = new ArrayList();
            }

        }

        /**
         * Behaviour for method
         * {@link de.innovationgate.webgate.api.WGDocument#getItemValue}
         */
        public Object getForGetItemValue() {
            return clone(forGetItemValue);
        }

        public void setForGetItemValue(Object forGetItemValue) {
            this.forGetItemValue = forGetItemValue;
        }

        /**
         * Behaviour for method {@link WGDocument#getItemValueList}
         */
        public List getForGetItemValueList() {
            return (List) clone(forGetItemValueList);
        }

        private Object clone(Object obj) {

            if (obj != null && obj instanceof List) {
                return new ArrayList((Collection) obj);
            }
            else {
                return obj;
            }

        }

        public void setForGetItemValueList(List forGetItemValueList) {
            this.forGetItemValueList = forGetItemValueList;
        }

        /**
         * Behaviour for method TMLForm.field(), when field exists but is empty
         */
        public Object getForTMLFormEmptyField() {
            return clone(forTMLFormEmptyField);
        }

        public void setForTMLFormEmptyField(Object forTMLFormEmptyField) {
            this.forTMLFormEmptyField = forTMLFormEmptyField;
        }

        /**
         * Behaviour for method TMLForm.fieldList, when field exists but is
         * empty
         */
        public List getForTMLFormEmptyFieldList() {
            return (List) clone(forTMLFormEmptyFieldList);
        }

        public void setForTMLFormEmptyFieldList(List forTMLFormEmptyFieldList) {
            this.forTMLFormEmptyFieldList = forTMLFormEmptyFieldList;
        }

        /**
         * Behaviour for method TMLForm.field()
         */
        public Object getForTMLFormField() {
            return clone(forTMLFormField);
        }

        public void setForTMLFormField(Object forTMLFormField) {
            this.forTMLFormField = forTMLFormField;
        }

        /**
         * Behaviour for method TMLForm.fieldList()
         */
        public List getForTMLFormFieldList() {
            return (List) clone(forTMLFormFieldList);
        }

        public void setForTMLFormFieldList(List forTMLFormFieldList) {
            this.forTMLFormFieldList = forTMLFormFieldList;
        }

        /**
         * Behaviour for method TMLContext.item()
         */
        public Object getForTMLItem() {
            return clone(forTMLItem);
        }

        public void setForTMLItem(Object forTMLItem) {
            this.forTMLItem = forTMLItem;
        }

        /**
         * Behaviour for method TMLContext.itemList()
         */
        public List getForTMLItemList() {
            return (List) clone(forTMLItemList);
        }

        public void setForTMLItemList(List forTMLItemList) {
            this.forTMLItemList = forTMLItemList;
        }

        /**
         * Behaviour for method {@link WGDocument#getItemText}
         */
        public String getForGetItemText() {
            return (String) clone(forGetItemText);
        }

        public void setForGetItemText(String forGetItemText) {
            this.forGetItemText = forGetItemText;
        }

        public String getCurrentBehaviour() {
            return behaviour;
        }

    }

    private static String USERCACHE_USERDETAILS = "userDetails";

    /**
     * Predefined creation option, determining how many cache entries the WGAPI
     * cache is allowed to hold in memory. Defaults to 2000.
     * @deprecated
     */
    public static final String COPTION_MEMORYCACHESIZE = "MemoryCacheSize";

    /**
     * Predefined creation option, controlling of the WGAPI Cache is allowed to
     * use disk overflow functionality, as far as the implementation supports
     * this. Defaults to false.
     * @deprecated
     */
    public static final String COPTION_CACHEOVERFLOW = "CacheOverflowOnDisk";

    /**
     * Predefined creation option, controlling if the WGA workflow should
     * automatically approve a document in workflow if it's publisher is also
     * the approver
     */
    public static final String COPTION_AUTOAPPROVE = "AutoApprove";
   
    /**
     * Predefined creation option, controlling if hierarchical reader fields on areas and
     * pages are in effect, defaulting to true. Setting this to false may improve
     * performance but will let those feld be ignored.
     */
    public static final String COPTION_PAGEREADERS_ENABLED = "PageReadersEnabled";

    /**
     * Java system property for turning on verbose cachemanagement in applog
     */
    public static final String SYSPROPERTY_VERBOSE_CACHEMANAGEMENT = "de.innovationgate.wga.cachemanagement.verbose";

    /**
     * Java system property for turning on verbose output when a mutable object
     * needs to be cloned in WGAPI
     */
    public static final String SYSPROPERTY_VERBOSE_MUTABLECLONING = "de.innovationgate.wga.mutablecloning.verbose";

    private static final boolean VERBOSE_CACHE_MANAGEMENT = Boolean.valueOf(System.getProperty(SYSPROPERTY_VERBOSE_CACHEMANAGEMENT)).booleanValue();

    private static final boolean VERBOSE_MUTABLE_CLONING = Boolean.valueOf(System.getProperty(SYSPROPERTY_VERBOSE_MUTABLECLONING)).booleanValue();

    private static final String SYSPROPERTY_VERBOSE_DOCUMENTINSTANTIATION = "de.innovationgate.wga.documentinstantiation.verbose";

    protected static final boolean VERBOSE_DOCUMENT_INSTANTIATION = Boolean.valueOf(System.getProperty(SYSPROPERTY_VERBOSE_DOCUMENTINSTANTIATION)).booleanValue();

    /**
     * Sysproperty to enable verbose backend access
     */
    public static final String SYSPROPERTY_VERBOSE_BACKENDACCESS = "de.innovationgate.wga.backend.verbose";

    private static final boolean VERBOSE_BACKEND_ACCESS = Boolean.valueOf(System.getProperty(SYSPROPERTY_VERBOSE_BACKENDACCESS)).booleanValue();;

    /**
     * Predefined creation option, determining how many seconds the update
     * monitoring should pause after an update has been processed. Defaults to 5
     * seconds.
     */
    public static final String COPTION_UPDATETIMEOUT = "UpdateTimeout";

    /**
     * Sets a db reference directly at creation time, so the WGAPI might use it
     * from the start
     */
    public static final String COPTION_DBREFERENCE = "DBReference";

    private int updateTimeoutSeconds = 5;

    /**
     * Represents an Entry in Query cache with all neccessary info
     */
    public class QueryCacheEntry {

        private WGCachedResultSet _resultSet;

        private String _fullQuery;

        public QueryCacheEntry(WGCachedResultSet resultSet, String fullQuery) {
            _resultSet = resultSet;
            _fullQuery = fullQuery;
        }

        /**
         * @return Returns the fullQuery.
         */
        protected String getFullQuery() {
            return _fullQuery;
        }

        /**
         * @return Returns the resultSet.
         */
        protected WGCachedResultSet getResultSet() {
            return _resultSet;
        }

    }

    /**
     * Represents the key of a query cache entry, containing query type, query
     * string and parameter
     */
    public class QueryCacheKey {

        private String _type;

        private String _query;

        private Map _parameters;

        public QueryCacheKey(String type, String query, Map parameters) {
            _type = type;
            _query = query;
            _parameters = new HashMap();

            // Convert parameter entries to comparable objects
            Iterator entries = parameters.entrySet().iterator();
            while (entries.hasNext()) {
                Map.Entry entry = (Map.Entry) entries.next();
                Object entryValue = entry.getValue();
                if (entryValue instanceof Comparable) {
                    _parameters.put(entry.getKey(), entryValue);
                }
                else if (entryValue instanceof WGDocument) {
                    _parameters.put(entry.getKey(), ((WGDocument) entryValue).getDocumentKey());
                }
                else {
                    _parameters.put(entry.getKey(), String.valueOf(entryValue));
                }
            }

        }

        public int hashCode() {
            final int PRIME = 31;
            int result = 1;
            result = PRIME * result + ((_parameters == null) ? 0 : _parameters.hashCode());
            result = PRIME * result + ((_query == null) ? 0 : _query.hashCode());
            result = PRIME * result + ((_type == null) ? 0 : _type.hashCode());
            return result;
        }

        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final QueryCacheKey other = (QueryCacheKey) obj;
            if (_parameters == null) {
                if (other._parameters != null)
                    return false;
            }
            else if (!_parameters.equals(other._parameters))
                return false;
            if (_query == null) {
                if (other._query != null)
                    return false;
            }
            else if (!_query.equals(other._query))
                return false;
            if (_type == null) {
                if (other._type != null)
                    return false;
            }
            else if (!_type.equals(other._type))
                return false;
            return true;
        }

    }

    /**
     * Contains statistics about a WGA session. Session statistics are initially
     * collected in WGSessionContext objects but thrown away with these objects
     * when the session exists. The SessionStatistic object can be used to
     * persist certain intersting statistics beyond the session lifetime.
     */
    public class SessionStatistic implements Comparable {

        private boolean _maxDocsExceeded;

        private String _task;

        private Date _created;

        private int _totalDocsRetrieved;

        /**
         * Constructor that will extract statistics from a session context.
         *
         * @param context
         *            The session context that is used for information retrieval
         */
        public SessionStatistic(WGSessionContext context) {
            _totalDocsRetrieved = context.getTotalFetchedCores();
            _created = context.getCreated();
            _task = context.getTask();
            _maxDocsExceeded = context.isMaxCoreMessageShowed();
        }

        /**
         * Returns the creation date of the session
         */
        public Date getCreated() {
            return _created;
        }

        /**
         * Returns if the "maxdocs"-Property of maximum documents per session
         * was exceeded by this session. If so, the session did drop it's first
         * retrieved backend documents while processing.
         */
        public boolean isMaxDocsExceeded() {
            return _maxDocsExceeded;
        }

        /**
         * Returns the task that the session did accomplish
         */
        public String getTask() {
            return _task;
        }

        /**
         * Returns, how many docs the session did retrieve from backend while it
         * was running
         */
        public int getTotalDocsRetrieved() {
            return _totalDocsRetrieved;
        }

        /*
         * (Kein Javadoc)
         *
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(Object o) {
            SessionStatistic otherStatistic = (SessionStatistic) o;
            return (getTotalDocsRetrieved() - otherStatistic.getTotalDocsRetrieved());
        }

    }

    /**
     * Shows, if the database monitors the last change date of the backend
     * database to maintain caches.
     */
    public boolean monitorLastChange() {
        return this.monitorLastChange;
    }

    private String title = null;

    /**
     * Accesslevel-Name for ACCESSLEVEL_EDITOR_DESIGNER
     */
    public static final String ALNAME_EDITOR_DESIGNER = "EDITOR/DESIGNER";

    /**
     * Accesslevel-Name for ACCESSLEVEL_READER_DESIGNER
     */
    public static final String ALNAME_READER_DESIGNER = "READER/DESIGNER";

    /**
     * Accesslevel-Name for ACCESSLEVEL_READER
     */
    public static final String ALNAME_READER = "READER";

    /**
     * Accesslevel-Name for ACCESSLEVEL_NOACCESS
     */
    public static final String ALNAME_NOACCESS = "NOACCESS";

    /**
     * Accesslevel-Name for ACCESSLEVEL_MANAGER
     */
    public static final String ALNAME_MANAGER = "MANAGER";

    /**
     * Accesslevel-Name for ACCESSLEVEL_EDITOR
     */
    public static final String ALNAME_EDITOR = "EDITOR";

    /**
     * Accesslevel-Name for ACCESSLEVEL_AUTHOR_DESIGNER
     */
    public static final String ALNAME_AUTHOR_DESIGNER = "AUTHOR/DESIGNER";

    /**
     * Accesslevel-Name for ACCESSLEVEL_AUTHOR
     */
    public static final String ALNAME_AUTHOR = "AUTHOR";

    private Map operations = WGUtils.createSynchronizedMap();

    /**
     * Converts access level access level names WGDatabase.ALNAME_... to codes
     * WGDatabase.ACCESSLEVEL_...
     *
     * @param accessLevel
     *            The access level code to convert
     * @return The access level name. If code is unknown returns "(unknown)"
     */
    public static String accessLevelText(int accessLevel) {

        switch (accessLevel) {

            case WGDatabase.ACCESSLEVEL_AUTHOR:
                return ALNAME_AUTHOR;
            case WGDatabase.ACCESSLEVEL_AUTHOR_DESIGNER:
                return ALNAME_AUTHOR_DESIGNER;
            case WGDatabase.ACCESSLEVEL_EDITOR:
                return ALNAME_EDITOR;
            case WGDatabase.ACCESSLEVEL_EDITOR_DESIGNER:
                return ALNAME_EDITOR_DESIGNER;
            case WGDatabase.ACCESSLEVEL_MANAGER:
                return ALNAME_MANAGER;
            case WGDatabase.ACCESSLEVEL_NOACCESS:
                return ALNAME_NOACCESS;
            case WGDatabase.ACCESSLEVEL_NOTLOGGEDIN:
                return "(not logged in)";
            case WGDatabase.ACCESSLEVEL_READER:
                return ALNAME_READER;
            case WGDatabase.ACCESSLEVEL_READER_DESIGNER:
                return ALNAME_READER_DESIGNER;
            default:
                return "(unknown)";

        }

    }

    /**
     * Converts access level names WGDatabase.ALNAME_... to codes
     * WGDatabase.ACCESSLEVEL_...
     *
     * @param accessLevel
     *            The access level name to convert
     * @return The code. If the name is unknown returns
     *         WGDatabase.ACCESSLEVEL_NOTLOGGEDIN
     */
    public static int accessLevelCode(String accessLevel) {

        if (accessLevel.equals(ALNAME_NOACCESS)) {
            return WGDatabase.ACCESSLEVEL_NOACCESS;
        }
        else if (accessLevel.equals((ALNAME_READER))) {
            return WGDatabase.ACCESSLEVEL_READER;
        }
        else if (accessLevel.equals(ALNAME_READER_DESIGNER)) {
            return WGDatabase.ACCESSLEVEL_READER_DESIGNER;
        }
        else if (accessLevel.equals(ALNAME_AUTHOR)) {
            return WGDatabase.ACCESSLEVEL_AUTHOR;
        }
        else if (accessLevel.equals(ALNAME_AUTHOR_DESIGNER)) {
            return WGDatabase.ACCESSLEVEL_AUTHOR_DESIGNER;
        }
        else if (accessLevel.equals(ALNAME_EDITOR)) {
            return WGDatabase.ACCESSLEVEL_EDITOR;
        }
        else if (accessLevel.equals(ALNAME_EDITOR_DESIGNER)) {
            return WGDatabase.ACCESSLEVEL_EDITOR_DESIGNER;
        }
        else if (accessLevel.equals(ALNAME_MANAGER)) {
            return WGDatabase.ACCESSLEVEL_MANAGER;
        }
        else {
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }
    }

    /**
     * Collects various statistics about this database.
     */
    public class WGDatabaseStatistics {

        /**
         * Returns the number of documents that currently reside in the document
         * cache
         */
        public int getDocumentCount() {
            return masterDocumentsByKey.size();
        }

        public int getCurrentUserContentCount() {
            return contentByKey.size();
        }

    }

    /**
     * Util class for finding the next free content version
     */
    public class FreeContentVersionFinder implements Runnable {

        private WGStructEntry _entry;

        private WGLanguage _language;

        private int _newVersion = -1;

        public FreeContentVersionFinder(WGStructEntry entry, WGLanguage language) {
            _entry = entry;
            _language = language;
        }

        public int findNewVersion() {
            Thread thread = new Thread(this);
            thread.start();
            try {
                thread.join();
            }
            catch (InterruptedException e) {
            }
            return _newVersion;
        }

        public void run() {
            try {
                openSession();

                int newVersion;

                // Test for the highest stored version.
                newVersion = 0;
                Iterator contentList = _entry.getAllContent(true).iterator();
                WGContent content;
                while (contentList.hasNext()) {
                    content = (WGContent) contentList.next();
                    if (content.getLanguage().equals(_language) && content.getVersion() > newVersion) {
                        newVersion = content.getVersion();
                    }
                }

                // Go up in version numbers. Test agains newly created content
                // keys (of contents that have not yet been stored).
                WGContentKey newContentKey = null;
                do {
                    newVersion++;
                    newContentKey = new WGContentKey(_entry.getStructKey(), _language.getName(), newVersion);
                } while (newContentKeys.containsKey(newContentKey));

                // Return found version
                _newVersion = newVersion;
            }
            catch (WGUnavailableException e) {
            }
            catch (WGAPIException e) {
            }
            finally {
                WGFactory.getInstance().closeSessions();
            }

        }

    }

    private UserHashMapGroup userHashMapGroup;

    // Special user names
    /**
     * Anonymous user name
     */
    public static final String ANONYMOUS_USER = Constants.ANONYMOUS_USER;

    /**
     * Dummy user name that is used in openSession-Method to insert a session
     * token. The token then should be entered as password.
     */
    public static final String SESSIONTOKEN_USER = "###SESSIONTOKEN###";

    /**
     * Dummy user name that is used on request based authmodules if
     * request.getRemoteUser() returns 'null'
     */
    public static final String UNKNOWN_REMOTE_USER = "###UNKNOWN_REMOTE_USER###";

    /**
     * The default media key
     */
    public static final String DEFAULT_MEDIAKEY = "html";

    /**
     * User hasn't logged in yet or login failed due to incorrect login
     * information
     */
    // Access levels
    /**
     * User nas no valid login
     */
    public static final int ACCESSLEVEL_NOTLOGGEDIN = -1;

    /**
     * User has not access to the database.
     */
    public static final int ACCESSLEVEL_NOACCESS = 0;

    /**
     * User can read documents but cannot create or edit them.
     */
    public static final int ACCESSLEVEL_READER = 10;

    /**
     * Like ACCESSLEVEL_READER, including ability to edit Languages, File
     * Containers, TML and CSS/JS modules
     */
    public static final int ACCESSLEVEL_READER_DESIGNER = 15;

    /**
     * User can create content documents but has no access to content documents,
     * not created by him
     */
    public static final int ACCESSLEVEL_AUTHOR = 20;

    /**
     * Like ACCESSLEVEL_AUTHOR, including ability to edit Languages, File
     * Containers, TML and CSS/JS modules
     */
    public static final int ACCESSLEVEL_AUTHOR_DESIGNER = 25;

    /**
     * User can create new content documents, and edit existing ones that he
     * didn't create.
     */
    public static final int ACCESSLEVEL_EDITOR = 30;

    /**
     * Like ACCESSLEVEL_EDITOR, including ability to edit Languages, File
     * Containers, TML and CSS/JS modules
     */
    public static final int ACCESSLEVEL_EDITOR_DESIGNER = 35;

    /**
     * Full access, creation and administration rights.
     */
    public static final int ACCESSLEVEL_MANAGER = 90;

    /**
     * Enhance the query with default settings (e.g. not to retrieve documents
     * that are set invisible). Set to Boolean(true/false).
     */
    public static final String QUERYOPTION_ENHANCE = "enhance";

    /**
     * Set to Boolean(true), to reduce query results to only released content
     */
    public static final String QUERYOPTION_ONLYRELEASED = "onlyReleased";

    /**
     * Set to a specific language code to reduce query results to content of
     * this language
     */
    public static final String QUERYOPTION_ONLYLANGUAGE = "onlyLanguage";

    /**
     * When set, excludes the WGContent object given as option value from the
     * query result
     */
    public static final String QUERYOPTION_EXCLUDEDOCUMENT = "excludeDocument";

    /**
     * This is an output option set by the implementation, that carries the
     * complete query that was executed, as it was modified by the
     * implementation.
     */
    public static final String QUERYOPTION_RETURNQUERY = "returnQuery";

    /**
     * Controls the maximum results of the query. Specify as new Integer(value).
     */
    public static final String QUERYOPTION_MAXRESULTS = "maxResults";

    /**
     * Cache the results of queries.
     */
    public static final String QUERYOPTION_CACHERESULT = "cacheResult";

    /**
     * Takes native options for the database implementation as commaseparated
     * key=value-Pairs. E.g. option1=value1,option2=value2...
     */
    public static final String QUERYOPTION_NATIVEOPTIONS = "nativeOptions";

    /**
     * Set to the role, that this query should act as, filtering contents that
     * should be invisible to that role. Default is
     * WGContent.DISPLAYTYPE_SEARCH. Compare WGContent.isHiddenFrom(). Use
     * Constants WGContent.DISPLAYTYPE_... as values.
     */
    public static final String QUERYOPTION_ROLE = "role";

    /**
     * The occurence of this queryoption in the query parameters after the
     * execution of the query shows, that the result set was retrieved from
     * query cache
     */
    public static final String QUERYOPTION_USEDCACHE = "usedCache";
   
    /**
     * Injects a priority list of languages to return in the query. Should override {@link #QUERYOPTION_ONLYLANGUAGE} if available.
     */
    public static final String QUERYOPTION_LANGUAGES = "languages";

    /**
     * This query option contains a map with query parameters, whose values
     * should be used by the underlying query mechanism to fill parameter parts
     * in the query text.
     */
    public static final String QUERYOPTION_QUERY_PARAMETERS = "queryParameters";;
   
   
    /**
     * Query type for {@link #queryUserProfileNames(String, String, Map)} that retrieves all existent user profiles
     */
    public static final String PROFILEQUERY_TYPE_ALL = "all";

    // Roles
    /**
     * Database serves content and supplementary docs like content type,
     * language if it is a full content store
     */
    public static final String ROLE_CONTENT = "content";

    /**
     * Database servers design documents (WebTML, CSS/JS)
     */
    public static final String ROLE_DESIGN = "design";

    /**
     * Database serves file containers
     */
    public static final String ROLE_REPOSITORY = "repository";

    /**
     * Database servers user profiles
     */
    public static final String ROLE_USERPROFILES = "userprofiles";

    /**
     * Content supports query languages.
     */
    public static final String FEATURE_QUERYABLE = "queryable";
   
    /**
     * The content store is able to also store user profiles and be its own "personalisation database"
     */
    public static final String FEATURE_SELF_PERSONALIZABLE = "selfPersonalizable";

    /**
     * DB has a native expression language
     */
    public static final String FEATURE_NATIVEEXPRESSIONS = "nativeExpressions";

    /**
     * Database is editable i.e. can modify data of it's backend
     */
    public static final String FEATURE_EDITABLE = "editable";

    /**
     * Content can be navigated hierarchical / is hierarchically ordered
     */
    public static final String FEATURE_HIERARCHICAL = "hierarchical";

    /**
     * Content database maintains a last changed property, that should be
     * monitored to track database changes.
     */
    public static final String FEATURE_LASTCHANGED = "lastChanged";

    /**
     * Content database can store complex values beyond string, number an dates
     * and performs type checking itself
     */
    public static final String FEATURE_COMPLEXVALUES = "complexValues";

    /**
     * Content database needs temporary storage of webtml variables in content
     * documents (e.g. to allow native expression languages to access them)
     */
    public static final String FEATURE_STORESVARS = "storesVars";

    /**
     * Content database has content types, struct entries, is multilingual and
     * maintains content versioning, so content document creation must receive
     * and generate all this info.
     */
    public static final String FEATURE_FULLCONTENTFEATURES = "fullContentFeatures";

    /**
     * Controls the behaviour when setting object relations for newly created
     * objects. If feature present, the objects themselves will be passed as
     * metadata. If not, only the name/key will be passed
     */
    public static final String FEATURE_USE_OBJECTS_AS_REFERENCES = "useObjectsAsReferences";

    /**
     * Controls, if this implementations's newly created struct entries can
     * generate their own keys
     */
    public static final String FEATURE_GENERATES_STRUCTKEYS = "generatesStructkeys";

    /**
     * Controls if this implementation's newly created struct entries can accept
     * keys from the creator
     */
    public static final String FEATURE_ACCEPTS_STRUCTKEYS = "acceptsStructkeys";
   
    /**
     * Feature that will prevent the WGAPI from limiting the fetched cores for a session
     * Should be set for DBs where limiting on WGAPI level makes no sense
     */
    public static final String FEATURE_UNLIMITED_CORES = "unlimitedCores";
   
    /**
     * Controls if this implementation is able to perform a backend login, i.e. the core itself
     * logs in with the user's login data. If this feature is activated and the database has
     * no authentication module configured, the login information is given to {@link WGDatabaseCore#openSession(AuthenticationSession, Object, boolean)}
     * as {@link BackendAuthSession}.
     */
    public static final String FEATURE_PERFORMS_BACKEND_LOGIN = "performsBackendLogin";

    /**
     * Shows if this database relies on authentication modules for external
     * authentication.
     */
    public static final String FEATURE_EXTERNAL_AUTHENTICATION = "externalAuthentication";

    /**
     * Controls if this WGAPI implementation implements the getAllContentKeys()
     * method
     */
    public static final String FEATURE_RETRIEVE_ALL_CONTENTKEYS = "retrieveAllContentKeys";

    /**
     * Shows, if this implementation supports querying user profiles by method
     * queryUserProfileNames
     */
    public static final String FEATURE_QUERY_PROFILES = "queryProfiles";

    /**
     * Automatically cascades deletions (i.e. deletes all dependent documents),
     * so that the WGAPI doesn't need to do that
     */
    public static final String FEATURE_AUTOCASCADES_DELETIONS = "autocascadesDeletions";

    /**
     * Controls if this implementation can take session tokens for a login
     */
    public static final String FEATURE_SESSIONTOKEN = "sessionToken";

    /**
     * Controls, if this implementation holds content in multiple languages
     */
    public static final String FEATURE_MULTILANGUAGE = "multilanguage";

    /**
     * Content database supports transactions
     */
    public static final String FEATURE_TRANSACTIONS = "transactions";

    /**
     * This feature instructs the WGAPI to reduce callbacks to the database on
     * some occassions where it is unlikely necessary to connect (Should
     * increase performance on implementations with slow database connections)
     */
    public static final String FEATURE_REDUCE_CALLBACKS = "reduceCallbacks";

    /**
     * ACL of this database is manageable via WGAPI
     */
    public static final String FEATURE_ACL_MANAGEABLE = "aclManageable";

    /**
     * Find updated documents since a cutoff date by core-method
     * getUpdateDocumentsSince
     */
    public static final String FEATURE_FIND_UPDATED_DOCS = "findUpdatedDocs";

    /**
     * Use the WGAPI to create a new database of this type (Method
     * WGFactory.createDatabase())
     */
    public static final String FEATURE_CREATEABLE = "createable";

    /**
     * determines that the underlying core has the feature to loadbalance
     * connections to multiple db servers, in this case connections are by
     * default readonly and each db updates have to be initialized by calling
     * beginUpdate()
     */
    public static final String FEATURE_LOADBALANCE = "loadbalance";

    /**
     * determines if documents in this database has the ability to validate file
     * attachments by calling doc.validateAttachments();
     */
    public static final String FEATURE_VALIDATE_ATTACHMENTS = "validateAttachments";

    /**
     * determines if the database supports real content relations
     */
    public static final String FEATURE_CONTENT_RELATIONS = "contentRelations";


    /**
     * determines if the database implementation has the ability to return document changes and modified dates on millisecond precision
     */
    public static final String FEATURE_MILLISECOND_PRECISION = "millisecondPrecision";
   
    /**
     * Enabling this feature will disable the lowercasing of metadata field values for fields that normally enforce this.
     */
    public static final String FEATURE_DISABLE_META_LOWERCASING = "disableMetaLowercasing";
   
    /**
     * Predefined creation option to specify configuration XML.
     * @deprecated
     */
    public static final String COPTION_XML = "XML";

    /**
     * Defines if caching is globally enabled for this database, defaults to
     * true. If false, the database will cache no document data. In this state
     * the WGAPI locking functions are not effective.
     */
    public static final String COPTION_CACHING_ENABLED = "CachingEnabled";

    /**
     * Predefined creation option, that defines how many "cores" (i.e. backend
     * documents) a session may retrieve at once. If more are retrieved, the
     * first cores will be dropped to stay under this threshold.
     */
    public static final String COPTION_MAXCORES = "MaxDocs";

    /**
     * Predefined creation option, that determines if the LastChange-Date of
     * this database should be monitored to drop cache when it changes.
     */
    public static final String COPTION_MONITORLASTCHANGE = "MonitorLastChange";

    /**
     * Specifies the document types that a design provider for the current db
     * should provide, taking a commaseparated list of typenames of design
     * documents. This option must be enforced by the provider itself.
     */
    public static final String COPTION_DESIGNPROVIDERTYPES = "DesignProviderDoctypes";

    /**
     * Predefined creation option, that determines the class name of a workflow
     * engine to use. If this is not set, the default workflow engine of the
     * implementation is used
     */
    public static final String COPTION_WORKFLOWENGINE = "WorkflowEngine";

    /**
     * Determines the behaviour of this database and functionalitites that use
     * this database regarding the retrieval of nonexistent items. Allowed
     * values: - "straight" - The default. Always return null. - "compatible" -
     * Compatibility with WGA 3.3. Use the values used there which differ by DB
     * implementation and functionality
     */
    public static final String COPTION_NOITEMBEHAVIOUR = "NoItemBehaviour";

    /**
     * Determines the latency of user specific caches that expire after the
     * given time (in minutes). If not specified or 0 the caches never expire.
     */
    public static final String COPTION_USERCACHELATENCY = "UserCacheLatency";

    /**
     * Determines if users at access level "READER" are allowed to create user
     * profiles in this database
     */
    public static final String COPTION_READERPROFILECREATION = "ReaderProfileCreation";
   
   
    /**
     * Option to directly determine the content store version of this database
     */
    public static final String COPTION_CONTENT_STORE_VERSION = "ContentStoreVersion";

    /**
     * Special DB attribute that is to be set while a batch process (e.g.
     * synchronization) is currently updating the database.
     */
    public static final String ATTRIB_UPDATED = "$updated";

    /**
     * CA loaded via publisher option POPTION_CA
     */
    private X509Certificate _currentCA;

    private long _currentCALastModified;

    /**
     * CRL loaded via publisher option POPTION_CRL
     */
    private X509CRL _currentCRL;

    private long _currentCRLLastModified;

    // Core object
    private WGDatabaseCore core = null;

    // Storing of information
    private volatile boolean ready = false;

    private boolean monitorLastChange = false;

    protected boolean cachingEnabled = true;

    /**
     * The revision indicator of data in this database. Actually the state
     * of the database that is represented by the current cache state.
     */
    private Comparable _revision = null;
   
    /**
     * The date that the current revision corresponds to
     */
    private Date _revisionDate = null;

    /**
     * Time of the last cache maintenance. This is currently only used for {@link #getLastCacheMaintenance()}.
     * The readonly cache functionality is now driven by #lastCacheMaintenanceNanos.
     */
    private Date lastCacheMaintenance = null;
   
    /**
     * Precise nano time of the last cache maintenance. Needed to disallow cache writing in sessions
     * that have started before this date and may have stale backend documents
     */
    private volatile long lastCacheMaintenanceNanos = Long.MIN_VALUE;
   
   

    private String masterLoginInputName = null;

    private String masterLoginName = null;

    private String masterLoginPassword = null;

    private String dbReference = "(none)";

    private String path = null;

    private boolean contentRole = false;

    private boolean designRole = false;

    private boolean repositoryRole = false;

    private boolean userProfilesRole = false;

    private int maxCores = 1000;

    private Map creationOptions = WGUtils.createSynchronizedMap();

    private Map customAttributes = WGUtils.createSynchronizedMap();

    private Map features = WGUtils.createSynchronizedMap();

    private SortedBag sessionStatistics = new TreeBag();

    // Session objects
    protected ThreadLocal sessionContext = new ThreadLocal();

    protected Map structParents = Collections.synchronizedMap(new HashMap());

    protected Map masterDocumentsByKey = WGUtils.createSynchronizedMap();

    protected UserHashMap contentByKey = null;

    protected UserHashMap contentByName = null;

    protected UserHashMap userCache = null;

    protected UserHashMap queryCache = null;

    protected Map designDocumentLists = WGUtils.createSynchronizedMap();

    // Stores newly created content keys (before the content gets saved)
    protected volatile Map<WGContentKey,Object> newContentKeys = new WeakHashMap<WGContentKey,Object>();

    // Event listener registration
    protected List databaseEventListeners = Collections.synchronizedList(new ArrayList());

    protected List databaseConnectListeners = Collections.synchronizedList(new ArrayList());

    protected List databaseDesignChangeListeners = Collections.synchronizedList(new ArrayList());

    protected List contentEventListeners = Collections.synchronizedList(new ArrayList());

    protected List workflowEventListeners = Collections.synchronizedList(new ArrayList());

    private WGDesignProvider designProvider = null;
   
    private WGSchemaDefinition schemaDefinition = null;

    protected WGWorkflowEngine workflowEngine = new WGDefaultWorkflowEngine();

    // Utility objects
    protected ThreadLocal recursiveEventSemaphore = new ThreadLocal();

    private long updateTimeout = Long.MIN_VALUE;

    private boolean allowCacheMaintenance = true;

    private Set unmodifiableDoctypes = new HashSet();

    private boolean readerProfileCreation = true;

    private boolean projectMode = false;

    /**
     * Defines, if incremental cache maintenance is allowed for this database.
     * Defaults to true (if the database type supports it). If set to false, the
     * database will completely clear all caches once the data is manipulated in
     * the background
     */
    public static final String COPTION_ALLOWCACHEMAINTENANCE = "AllowCacheMaintenance";

    /**
     * Defines if an online deletion check for documents should be enabled which
     * costs performance. This is disabled by default.
     */
    public static final String COPTION_DELETIONCHECK = "DeletionCheck";

    /**
     * Enables/Disables project mode. In project mode no versioning and
     * workflows are used.
     */
    public static final String COPTION_PROJECTMODE = "ProjectMode";

    /**
     * The user name that is returned for the user of a WGA master session
     */
    public static final String MASTER_USERNAME = "WGA Master Session User";



    /**
     * Number of documents in a cache list that may need to be retrieved to rebuild the cache. If more docs need to be retrieved the cache will not be used.
     */
    public static final String COPTION_LIST_CACHE_REBUILD_THRESHOLD = "ListCacheRebuildThreshold";

    // lockMangager for locks of objects in this db
    private LockManager _lockManager = new LockManager();
   
    private AuthenticationModule _authenticationModule = null;

    /**
     * Injected default language by outer code
     */
    private volatile String _defaultLanguage = null;

    private volatile boolean connected = false;

    private String type;

    private String typeName;

    private int userCacheLatency = 30;

    private Collator _defaultLanguageCollator = null;

    private NoItemBehaviour noItemBehaviour;

    private boolean deletionCheck = false;

    private List _connectActions = new ArrayList();

    private boolean autoApprove = true;
    private boolean pageReadersEnabled = true;

    private int listCacheRebuildThreshold = 10;

    private WGDatabaseServer server;

    private boolean _defaultLanguageAutoDetermined = false;

    /**
     * Option to disable certificate authentication even when the auth module to use has certauth enabled
     */
    public static final String COPTION_DISABLE_CERTAUTH = "DisableCertAuth";

    /**
     * A query option used on non-iterating query types, determining the size of a results fetch page to retrieve from the backend
     */
    public static final Object QUERYOPTION_FETCHSIZE = "fetchsize";

    /**
     * Protected constructor. Construction from outside only via WGFactory
     * object.
     *
     * @param server
     *            The database server that this database resides on
     * @param strType
     *            Type of database implementation (i.e. full qualified name of
     *            the database implementation class)
     * @param strPath
     *            Path to database. Interpreted by the implementation and
     *            therefor varying by implementation
     * @param strUserName
     *            Login user name
     * @param strUserPwd
     *            Password of user
     * @param options
     *            Map of implementation specific options.
     * @throws WGAPIException
     */
    protected WGDatabase(WGDatabaseServer server, String strType, String strPath, String strUserName, String strUserPwd, Map options, boolean prepareOnly) throws WGAPIException {

        // Initialize basic members
        this.server = server;
        this.type = strType;
        this.path = strPath;
        this.masterLoginInputName = strUserName;
        this.masterLoginName = strUserName;
        this.masterLoginPassword = strUserPwd;

        this.userHashMapGroup = new UserHashMapGroup();
        this.contentByKey = userHashMapGroup.newUserHashMap("dbCacheContentByKey");
        this.contentByName = userHashMapGroup.newUserHashMap("dbCacheContentByName");
        this.userCache = userHashMapGroup.newUserHashMap("dbCacheUserCache");
        this.queryCache = userHashMapGroup.newUserHashMap("dbCacheQueryCache");

        // Setting creation options
        if (options != null) {
            WGUtils.putAllNonNullValues(options, this.creationOptions);
        }

        if (this.creationOptions.containsKey(COPTION_DBREFERENCE)) {
            setDbReference(String.valueOf(this.creationOptions.get(COPTION_DBREFERENCE)));
        }
        else {
            setDbReference(getPath());
        }

        // Try to get implementation class and open database
        connectDBCore(prepareOnly);

        this.ready = true;
       
        // Execute connect actions that may have been registered while the db got connected
        if (isConnected()) {
            notifyConnectActions();
        }


    }

    private void createDBCoreObject(String strType) throws WGInvalidDatabaseException {
        Class dbClass;
        try {
            dbClass = Class.forName(strType, true, WGFactory.getImplementationLoader());

            if (!WGDatabaseCore.class.isAssignableFrom(dbClass)) {
                throw new WGInvalidDatabaseException("Cannot allocate db type " + strType + ": is no implementation of " + WGDatabaseCore.class.getName());
            }
            this.core = (WGDatabaseCore) dbClass.newInstance();
        }
        catch (ClassNotFoundException exc) {
            throw new WGInvalidDatabaseException("Database implementation class or dependent class not found. Check classpath: " + exc.getMessage());
        }
        catch (NoClassDefFoundError err) {
            throw new WGInvalidDatabaseException("Database implementation class or dependent class not found. Check classpath: " + err.getMessage());
        }
        catch (IllegalAccessException exc) {
            throw new WGInvalidDatabaseException("Database implementation class or dependent class not accessible: " + exc.getMessage());
        }
        catch (InstantiationException exc) {
            throw new WGInvalidDatabaseException("Could not instantiate implementation class: " + exc.getMessage());
        }
    }

    /**
     * Creates the connection to the database backend, instantiates and opens
     * the db core
     *
     * @param prepareOnly
     *            Specify false to only prepare the database for connection, but
     *            leave it unconnected after this method, standing by for the
     *            first session to be opened.
     * @return true, if the method connected the core, false if not (when the
     *         core already was connected)
     * @throws WGAPIException
     */
    /**
     * @param prepareOnly
     * @return
     * @throws WGAPIException
     */
    private synchronized boolean connectDBCore(boolean prepareOnly) throws WGAPIException {
        WGUserAccess userAccess;

        // Prevent running when already connected
        if (connected) {
            return false;
        }

        // / Create core object
        createDBCoreObject(this.type);

        // Open database
        userAccess = this.core.open(this, path, masterLoginName, masterLoginPassword, prepareOnly);
        if (userAccess.getAccessLevel() <= WGDatabase.ACCESSLEVEL_NOACCESS) {
            throw new WGInvalidDatabaseException("Master login " + masterLoginName + " either has wrong password or no access to database");
        }

        // Initialize data from core

        if (this.title == null) {
            this.title = this.core.getTitle();
        }

        this.typeName = this.core.getTypeName();

        // Initialize CS roles
        Collection roles = new ArrayList(this.core.getRoles());
        if (roles.contains(WGDatabase.ROLE_CONTENT)) {
            this.contentRole = true;
        }
        if (roles.contains(WGDatabase.ROLE_DESIGN)) {
            this.designRole = true;
        }
        if (roles.contains(WGDatabase.ROLE_REPOSITORY)) {
            this.repositoryRole = true;
        }
        if (roles.contains(WGDatabase.ROLE_USERPROFILES)) {
            this.userProfilesRole = true;
        }

        // Initialize max cores setting
        if (creationOptions.containsKey(COPTION_MAXCORES)) {
            try {
                maxCores = Integer.parseInt((String) creationOptions.get(COPTION_MAXCORES));
            }
            catch (NumberFormatException e) {
                WGFactory.getLogger().error("Cannot interpret creation option " + COPTION_MAXCORES + " as an integer. Defaulting to " + maxCores);
            }
            if (maxCores == 0) {
                maxCores = Integer.MAX_VALUE;
            }
        }

        // Initialize background monitoring for database changes
        if (!prepareOnly) {
            this.monitorLastChange = this.core.hasFeature(FEATURE_LASTCHANGED);

            if (creationOptions.containsKey(COPTION_MONITORLASTCHANGE)) {
                monitorLastChange = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_MONITORLASTCHANGE))).booleanValue();
            }
        }

        // Initialize caching enablement
        if (creationOptions.containsKey(COPTION_CACHING_ENABLED)) {
            cachingEnabled = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_CACHING_ENABLED))).booleanValue();
        }

        // Initialize cache maintenance
        if (creationOptions.containsKey(COPTION_ALLOWCACHEMAINTENANCE)) {
            allowCacheMaintenance = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_ALLOWCACHEMAINTENANCE))).booleanValue();
        }

        // Online deletion check for documents
        if (creationOptions.containsKey(COPTION_DELETIONCHECK)) {
            deletionCheck = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_DELETIONCHECK))).booleanValue();
        }

        // Project mode
        if (creationOptions.containsKey(COPTION_PROJECTMODE)) {
            projectMode = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_PROJECTMODE))).booleanValue();
            WGFactory.getLogger().info("Enabled project mode for database " + getDbReference());
        }

        // Initialize reader profile creation
        if (creationOptions.containsKey(COPTION_READERPROFILECREATION)) {
            readerProfileCreation = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_READERPROFILECREATION))).booleanValue();
        }

        if (creationOptions.containsKey(COPTION_AUTOAPPROVE)) {
            autoApprove = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_AUTOAPPROVE))).booleanValue();
        }
       
        if (creationOptions.containsKey(COPTION_AUTOAPPROVE)) {
            autoApprove = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_AUTOAPPROVE))).booleanValue();
        }
       
        pageReadersEnabled = this.core.hasFeature(FEATURE_FULLCONTENTFEATURES) && this.core.getContenStoreVersion() >= WGDatabase.CSVERSION_WGA5;
        if (creationOptions.containsKey(COPTION_PAGEREADERS_ENABLED)) {
            pageReadersEnabled = Boolean.valueOf(String.valueOf(creationOptions.get(COPTION_PAGEREADERS_ENABLED))).booleanValue();
        }

        // Initialize update timeout for background changes
        if (creationOptions.containsKey(COPTION_UPDATETIMEOUT)) {
            try {
                updateTimeoutSeconds = Integer.parseInt((String) creationOptions.get(COPTION_UPDATETIMEOUT));
                WGFactory.getLogger().info("Update timeout is set to " + updateTimeoutSeconds + " seconds.");
            }
            catch (NumberFormatException e) {
                WGFactory.getLogger().error("Cannot parse db option " + COPTION_UPDATETIMEOUT + " value " + creationOptions.get(COPTION_UPDATETIMEOUT));
            }
        }

        // Initialize user cache latency
        if (creationOptions.containsKey(COPTION_USERCACHELATENCY)) {
            String latencyStr = (String) creationOptions.get(COPTION_USERCACHELATENCY);
            try {
                userCacheLatency = Integer.parseInt(latencyStr);
            }
            catch (NumberFormatException e) {
                WGFactory.getLogger().error("Cannot parse db option " + COPTION_USERCACHELATENCY + " value " + creationOptions.get(COPTION_USERCACHELATENCY));
            }
        }

        // Initialize list cache rebuild threshold
        if (creationOptions.containsKey(COPTION_LIST_CACHE_REBUILD_THRESHOLD)) {
            String thresholdStr = (String) creationOptions.get(COPTION_LIST_CACHE_REBUILD_THRESHOLD);
            try {
                listCacheRebuildThreshold = Integer.parseInt(thresholdStr);
                WGFactory.getLogger().info("User list cache rebuild threshold is set to " + listCacheRebuildThreshold);
            }
            catch (NumberFormatException e) {
                WGFactory.getLogger().error("Cannot parse db option " + COPTION_LIST_CACHE_REBUILD_THRESHOLD + " value " + thresholdStr);
            }
        }

        if (userCacheLatency != 0) {
            contentByKey.setMapLatency(userCacheLatency * 1000 * 60);
            contentByName.setMapLatency(userCacheLatency * 1000 * 60);
            userCache.setMapLatency(userCacheLatency * 1000 * 60);
            queryCache.setMapLatency(userCacheLatency * 1000 * 60);
        }

        // Init last modified date
        updateRevision(this.core.getRevision());

        // Init value of nonexistent items
        noItemBehaviour = new NoItemBehaviour();
        if (creationOptions.containsKey(COPTION_NOITEMBEHAVIOUR)) {
            String noItemValueProp = (String) creationOptions.get(COPTION_NOITEMBEHAVIOUR);
            if (noItemValueProp.equalsIgnoreCase("compatible")) {
                noItemBehaviour.compatibleForDBImplementation(core);
            }
        }

        // If the db should only get prepared, close the core again
        if (prepareOnly) {
            core.close();
            // core = null; Not safe. Many processes rely on an existing core
            connected = false;
        }

        // Else open a master session
        else {
            // Open a session context
            WGSessionContext sessionContext = new WGSessionContext(this, MasterLoginAuthSession.getInstance(), null, userAccess);
            this.setSessionContext(sessionContext);

            /*
             * // Initialize item/meta cache try { _cache = new
             * WGAPIEHCache(2000); } catch (IOException e) { throw new
             * WGInvalidDatabaseException("Could not initialize api cache
             * because of " + e.getClass().getName() + " - " + e.getMessage());
             * }
             */

            // Initialize default language collator
            try {
                _defaultLanguageCollator = Collator.getInstance(WGLanguage.languageNameToLocale(getDefaultLanguage()));
            }
            catch (Exception e) {
                WGFactory.getLogger().error("Error determining default language collator. Using default platform collator", e);
                _defaultLanguageCollator = Collator.getInstance();
            }
           
            // Initialize custom workflow engine
            initWorkflowEngine();

            connected = true;
        }

        return true;
    }

    private void initWorkflowEngine() throws WGConfigurationException {
       
        Class engineClass = null;
       
        // Custom workflow engine configured by creation option
        if (getCreationOptions().containsKey(COPTION_WORKFLOWENGINE)) {
            String engineClassName = (String) this.getCreationOptions().get(COPTION_WORKFLOWENGINE);
            try {
                engineClass = WGFactory.getImplementationLoader().loadClass(engineClassName);
            }
            catch (ClassNotFoundException e) {
                throw new WGConfigurationException("Cannot load Workflow engine class " + engineClassName, e);
            }
        }
       
        // Dedicated workflow engine for the current db implementation
        else if (getCore().getDedicatedWorkflowEngine() != null) {
            engineClass = getCore().getDedicatedWorkflowEngine();
        }
       
        // Default workflow engine
        else {
            engineClass = WGFactory.getDefaultWorkflowEngine();
        }
       
        if (!WGWorkflowEngine.class.isAssignableFrom(engineClass)) {
            throw new WGConfigurationException("Workflow engine class " + engineClass.getName() + " does not implement " + WGWorkflowEngine.class.getName());
        }

        try {
            WGWorkflowEngine engine;
            if (WGFactory.getModuleRegistry() != null) {
                engine = (WGWorkflowEngine) WGFactory.getModuleRegistry().instantiate(engineClass);
            }
            else {
                engine = (WGWorkflowEngine) engineClass.newInstance();
            }
            engine.init(this);
            workflowEngine = engine;
           
        }
        catch (Exception e) {
            throw new WGConfigurationException("Exception initializing workflow engine " + engineClass.getName(), e);
        }
       
    }

    /**
     * Triggers checking for database updates.
     *
     * @throws WGAPIException
     */
    protected synchronized boolean checkDatabaseUpdates() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (this.monitorLastChange == false) {
            return false;
        }

        // No updates while in transaction
        if (getSessionContext().isTransactionActive()) {
            return false;
        }

        // Update timeout?
        if (updateTimeout != 0 && updateTimeout >= System.currentTimeMillis()) {
            return false;
        }
        updateTimeout = 0;

        // This method is already called higher in the call stack - so do
        // nothing
        if (this.recursiveEventSemaphore.get() != null) {
            return false;
        }
        this.recursiveEventSemaphore.set(new Boolean(true));
        try {
            // Outer test, if updating is needed. Faster, bc. there is no
            // synchronisation
            if (isDatabaseUpdatedInBackground()) {

                synchronized (this) {

                    // When in synchronized block, test again, bc. another
                    // thread may already have performed update while
                    // this task was waiting for the sync lock to be released
                    Comparable newLastChanged = getCore().getRevision();
                    if (isDatabaseUpdatedInBackground()) {

                        verboseCacheManagement("Cache management starting. Database date changed from " + this._revision + " to " + newLastChanged);

                        // No other task performing update at the moment - so
                        // refresh right here right now
                        if (hasFeature(FEATURE_FIND_UPDATED_DOCS) && allowCacheMaintenance == true) {
                            List changeLogs = getCore().getUpdateLogs(_revision);
                            processChanges(changeLogs);
                        }
                        else {
                            refresh();
                        }

                        // Update some flags, clear some (not maintainable)
                        // caches and fire event
                        updateRevision(newLastChanged);
                        this.getSessionContext().setDatabaseUpdated(true);
                       
                        // No more neccessary? Is done in processChanges() or refresh()
                        // this.fireDatabaseEvent(new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_UPDATE, null));
                       

                        this.getCore().refresh();
                        updateTimeout = System.currentTimeMillis() + (1000 * updateTimeoutSeconds);
                        verboseCacheManagement("Cache management ended");
                        return true;

                    }
                }
            }

            return false;

        }
        finally {
            this.recursiveEventSemaphore.set(null);
        }
    }

    /**
     * Returns if the database has been updated in background and caches may not
     * reflect the most up-to-date state, because cache management has not yet
     * processed these updates
     */
    public boolean isDatabaseUpdatedInBackground() throws WGAPIException {
        Comparable newLastChanged = getCore().getRevision();
        return (newLastChanged != null && !newLastChanged.equals(this._revision));
    }

    /**
     * Processes a list of updates, that occured in the background. Caches will
     * be maintained and events will be fired.
     *
     * @param updatedDocuments
     *            List of WGUpdateLog objects, representing database updates
     * @throws WGAPIException
     */
    private void processChanges(List updatedDocuments) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        verboseCacheManagement("Performing incremental cache update");

        if (getSessionContext().isTransactionActive() || !hasFeature(FEATURE_FIND_UPDATED_DOCS)) {
            return;
        }

        if (updatedDocuments == null) {
            WGFactory.getLogger().error("No updated documents provided for change processing");
            return;
        }

        // Iterate through update events
        Iterator changes = updatedDocuments.iterator();
        while (changes.hasNext()) {
            Thread.yield();
            WGUpdateLog log = (WGUpdateLog) changes.next();

            // Filter out rows that do not refer to document updates (like ACL
            // entries)
            if (log.getDocumentKey().startsWith("$")) {
                continue;
            }

            WGDocumentKey docKey = new WGDocumentKey(log.getDocumentKey());

            // Perform all neccessary operation for the given type of update
            if (log.getType() == WGUpdateLog.TYPE_UPDATE) {
                clearNonexistentDocIndicatorForDocument(log.getDocumentKey());
                WGDocument doc = getDocumentByDocumentKey(log.getDocumentKey());
                verboseCacheManagement("Performing creation/update of doc " + log.getDocumentKey());
                documentSaved(doc, docKey, true, false, false);

                // If metadata module was updated in background we drop the complete cache
                // as it might symbolize the end of a batch update process (F00004402)
                if (log.getDocumentKey().startsWith(WGCSSJSModule.METADATA_MODULE_QUALIFIER)) {
                    verboseCacheManagement("Update of metadata module " + log.getDocumentKey() + ". Complete cache refresh");
                    refresh();
                }

            }
            else if (log.getType() == WGUpdateLog.TYPE_DELETE) {
                try {
                    WGDocument doc = getDocumentByDocumentKeyFromCache(new WGDocumentKey(log.getDocumentKey()), true);
                    verboseCacheManagement("Performing deletion of doc " + log.getDocumentKey());
                    remove(doc, docKey, false);
                }
                catch (WGDocumentDoesNotExistException e) {
                    // NOOP: Was deleted and already marked as nonexistent
                }
            }
            else if (log.getType() == WGUpdateLog.TYPE_STRUCTMOVE) {
                WGStructEntry struct = (WGStructEntry) getDocumentByDocumentKey(log.getDocumentKey());
                if (struct != null) {
                    verboseCacheManagement("Performing struct movage of doc " + log.getDocumentKey());
                    processMovedDocuments(struct);
                }
            }
            else {
                WGFactory.getLogger().warn("Unknown log type: " + log.getType());
            }
        }

        // Throw global update event so event listeners may update
        this.fireDatabaseEvent(new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_UPDATE));

    }

    /**
     * Removes a nodoc placeholder for the given document to be able to load
     * this document when it just has been created
     *
     * @param documentKey
     *            The document key of the document
     */
    private void clearNonexistentDocIndicatorForDocument(String documentKey) {

        WGDocumentKey key = new WGDocumentKey(documentKey);
        switch (key.getDocType()) {

            case WGDocument.TYPE_CONTENT:

                clearNonexistentDocIndicator(this.contentByKey, key.getName());

                // Unable to do so bc. we do not know the name at that point
                // Since processChanges() will fetch the doc and map it
                // afterwards this is not really neccessary
                // clearNoDocPlaceholder(this.contentByName, key.getName());
                break;

            case WGDocument.TYPE_USERPROFILE:
            case WGDocument.TYPE_STRUCTENTRY:
            case WGDocument.TYPE_AREA:
            case WGDocument.TYPE_CONTENTTYPE:
            case WGDocument.TYPE_LANGUAGE:
            case WGDocument.TYPE_CSSJS:
            case WGDocument.TYPE_TML:
                // No special caches to clear
                break;

            default:
                // Non-document documentkeys (e.g. ACLEntries) are ignored
                return;

        }

        // Clear master cache
        clearNonexistentDocIndicator(masterDocumentsByKey, key);

    }

    private void clearNonexistentDocIndicator(Map cache, Object cacheKey) {
        Object entry = cache.get(cacheKey);
        if (entry instanceof NullPlaceHolder) {
            cache.remove(cacheKey);
        }
    }

    private void verboseCacheManagement(String msg) {
        if (VERBOSE_CACHE_MANAGEMENT) {
            WGFactory.getLogger().info("Cache Management DB:" + getDbReference() + " - " + msg);
        }
    }

    protected void verboseMutableCloning(Object obj) {
        if (VERBOSE_MUTABLE_CLONING) {
            WGFactory.getLogger().info("Mutable cloning DB:" + getDbReference() + " - Object of type " + obj.getClass().getName());
        }
    }

    protected void verboseBackendAccess(int operation, Object key) {
        if (VERBOSE_BACKEND_ACCESS) {
            WGFactory.getLogger().info(
                    "Backend Access DB:" + getDbReference() + " - Operation: " + WGOperationKey.getOperationName(operation) + " - Key: " + String.valueOf(key) + " - Cache: "
                            + (!getSessionContext().isCachingEnabled() ? "disabled" : !getSessionContext().isCacheWritingEnabled() ? "readonly" : "enabled") + " - Session: "
                            + getSessionContext().hashCode() + " - Task: " + getSessionContext().getTask());
        }
    }

    /**
     * Refreshes the data in database, testing for changes in the backend and
     * clearing all caches.
     *
     * @throws WGAPIException
     */
    public synchronized void refresh() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        verboseCacheManagement("Performing complete cache refresh");

        if (getSessionContext().isTransactionActive()) {
            //clear session cache           
            getSessionContext().clearCache();
            getCore().refresh();
            return;
        }

        // obtain a dblock - to ensure no documents are locked
        this.lock();

        this.getSessionContext().setDatabaseUpdated(true);

        synchronized (this.masterDocumentsByKey) {
            Iterator docs = this.masterDocumentsByKey.values().iterator();
            while (docs.hasNext()) {
                Object obj = docs.next();
                if (obj instanceof WGDocument) {
                    ((WGDocument) obj).dispose();
                }
            }
        }

        this.clearDocumentMappings();
        this.getCore().refresh();
        if (_authenticationModule != null) {
            _authenticationModule.clearCache();
            
        }
        getUserCache().clear();
        getSessionContext().clearCache();
        this.fireDatabaseEvent(new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_UPDATE));

        // unlock db
        this.unlock();
    }

    /**
     * Closes the database session. The database object and all child objects
     * must not be used until a new session has been opened.
     *
     * @throws WGAPIException
     */
    protected void closeSession() throws WGAPIException {
        WGFactory.getLogger().debug("WGDatabase close session " + path);

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        // Notify design provider
        if (designProvider != null) {
            designProvider.closeSession();
        }

        // No more neccessary bc. all resources hang at session context
        // dropAllDocumentCores(false);

        try {
            this.getCore().closeSession();
        }
        finally {
            WGSessionContext oldContext = getSessionContext();
            oldContext.dropResources();
            if (oldContext != null && oldContext.isMaxCoreMessageShowed()) {
                addToStatistics(oldContext);
            }

            // release obtained locks of sessioncontext
            getLockManager().releaseAllLocks(oldContext);

            this.setSessionContext(null);
        }
    }

    /**
     * Called to drop all retrieved document cores for this session at once.
     *
     * @param untimelyDispose
     */
    public void dropAllDocumentCores(boolean untimelyDispose) {

        getSessionContext().dropAllDocumentCores(untimelyDispose);

        /*
         * Drop cores of persistent documents synchronized (this.documents) {
         *
         * Iterator docs = this.documents.iterator(); WeakReference docRef;
         * while (docs.hasNext()) { docRef = (WeakReference) docs.next(); if
         * (docRef.get() != null) { ((WGDocument)
         * docRef.get()).dropCore(untimelyDispose, false); } else {
         * docs.remove(); } } }
         *
         * // Drop cores of temporary documents List docsList =
         * getTempDocuments(); Iterator docs = docsList.iterator(); WGDocument
         * doc; while (docs.hasNext()) { doc = (WGDocument) docs.next();
         * doc.dropCore(untimelyDispose, false); } docsList.clear();
         */

    }

    /**
     * Adds a session statistic about the current session to collected session
     * statistics.
     *
     * @param oldContext
     */
    private void addToStatistics(WGSessionContext oldContext) {

        sessionStatistics.add(new SessionStatistic(oldContext));
        if (sessionStatistics.size() > 100) {
            sessionStatistics.remove(sessionStatistics.first());
        }

    }

    /**
     * Closes and reopens the session with the given user information
     *
     * @param username
     * @param credentials
     * @return the access level of the new session
     * @throws WGAPIException
     */
    public int reopenSession(String username, Object credentials) throws WGAPIException {

        if (isSessionOpen()) {
            closeSession();
        }

        return openSession(username, credentials);

    }

    /**
     * Closes and reopens the session with the current logged in user. If no
     * session is open, opens a master session.
     *
     * @return The access level in the new session
     * @throws WGAPIException
     */
    public int reopenSession() throws WGAPIException {

        if (!isSessionOpen() || getSessionContext().isMasterSession()) {
            return reopenSession(null, null);
        }
        else {
            return reopenSession(getSessionContext().getUser(), getSessionContext().getCredentials());
        }

    }

    /**
     * Removed a document and does all neccessary operations after that. -
     * Maintain caches - Throw events - Set states Either parameter document or
     * log must be present
     *
     * @param document
     *            The document that is/was removed. Can be null if it was
     *            deleted in background and not in cache
     * @param docKey
     *            The document key of the document removed
     * @param removedByWGAPI
     *            true if the removing is triggered by WGAPI and should be
     *            executed in this method.
     * @return True if deletion succeeded
     * @throws WGAPIException
     */
    protected boolean remove(WGDocument document, WGDocumentKey docKey, boolean removedByWGAPI) throws WGAPIException {

        // Prepare deletion
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        // Only neccessary if document was in cache = document is present
        boolean dropQueryCache = false;
        List docsToDropCache = new ArrayList();
        String contentType = null;
        if (document != null) {
            // check document lock
            if (document.getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
                throw new ResourceIsLockedException("Unable to remove document. Document is locked.");
            }

            // check child locks of document for current session context
            if (getLockManager().foreignChildLocksExists(document, getSessionContext())) {
                throw new ResourceIsLockedException("Unable to remove document. Child objects of the object you want to remove are locked.");
            }

            // Collect document whose cache should be cleared when deletion was
            // successful
            if (document instanceof WGContent) {
                WGContent content = (WGContent) document;
                if (content.hasCompleteRelationships()) {
                    contentType = content.getStructEntry().getContentType().getName();
                }

                docsToDropCache.add(content.getStructEntry());
                if (content.getRetrievalStatus().equals(WGContent.STATUS_RELEASE)) {
                    dropQueryCache = true;
                }

            }
            else if (document instanceof WGStructEntry) {
                WGStructEntry structEntry = (WGStructEntry) document;
                WGDocument parent = getStructParent(structEntry.getStructKey(), true);
                if (parent != null) {
                    docsToDropCache.add(parent);
                }
            }
        }
        // Must be done in case of temporary content that influenced the
        // query
        // cache
        else if (docKey.getDocType() == WGDocument.TYPE_CONTENT) {
            dropQueryCache = true;
        }

        boolean result = true;
        synchronized (this) {

            // Only neccessary/possible if document was in cache = document is
            // present
            WGDocument cachedDocument = null;
            if (document != null) {
                cachedDocument = unmapDocumentObject(document);

                // Delete
                if (removedByWGAPI) {
                    result = document.getCore().remove();
                    if (result == true) {
                        updateRevision(getCore().getRevision());
                    }
                }
            }

            // Post deletion
            if (result == true) {

                // Update doc object status if present
                if (document != null) {
                    document.setEdited(false);
                    document.setDeleted(true);
                }
                if (cachedDocument != null && cachedDocument != document) {
                    document.setEdited(false);
                    document.setDeleted(true);
                }

                // Clear caches of related documents
                Iterator dropCacheIt = docsToDropCache.iterator();
                while (dropCacheIt.hasNext()) {
                    ((WGDocument) dropCacheIt.next()).dropCache();
                }

                // Clear indirect caches
                if (isDesignDocumentType(docKey.getDocType())) {
                    designDocumentLists.clear();
                }

                userCache.clear();
                if (dropQueryCache) {
                    queryCache.clear();
                }

                // Fire content has been deleted event
                if (docKey.getTypename().equals(WGDocument.TYPENAME_CONTENT)) {
                    WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_HASBEENDELETED, docKey.toString(), contentType, this);
                    if (document != null) {
                        event.setContent((WGContent) document);
                    }
                    fireContentEvent(event);
                }

                // Fire design change event
                if (docKey.getTypename().equals(WGDocument.TYPENAME_FILECONTAINER) || docKey.getTypename().equals(WGDocument.TYPENAME_TML) || docKey.getTypename().equals(WGDocument.TYPENAME_CSSJS)) {
                    // Only fire when no design provider present. Otherwise the
                    // listeners are registered at the provider and it is his
                    // responsibility to throw the event
                    if (getDesignProvider() == null) {
                        List logs = new ArrayList();
                        logs.add(new WGUpdateLog(WGUpdateLog.TYPE_DELETE, new Date(), getSessionContext().getUser(), docKey.toString()));
                        fireDatabaseDesignChangedEvent(new WGDesignChangeEvent(null, this, logs));
                    }
                }

                // Remove the session context, so we won't refetch it by the
                // cached document there
                getSessionContext().removeDocumentContext(docKey);

                // Fire database event if the remove has been done by WGAPI
                // When this is done in background change processing, the event
                // will get fired after all changes by checkDatabaseUpdates
                updateCacheMaintenanceData();
                if (removedByWGAPI) {
                    this.getSessionContext().setDatabaseUpdated(true);
                    fireDatabaseEvent(new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_UPDATE, docKey));
                }
            }
        }

        return result;

    }

    /**
     * Returns the area with the given name
     *
     * @param strName
     *            Name of the area
     * @return WGArea
     * @throws WGAPIException
     */
    public WGArea getArea(String strName) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return (WGArea) getDesignObject(WGDocument.TYPE_AREA, strName, null);

    }

    /**
     * Returns a map of all areas of this database, mapped by their area name.
     *
     * @return WGAreaMap
     * @throws WGAPIException
     */
    public WGAreaMap getAreas() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        List areasList = getDesignObjects(WGDocument.TYPE_AREA);
        if (areasList == null) {
            return null;
        }

        Iterator areas = areasList.iterator();
        TreeMap areasMap = new TreeMap();

        WGArea area;
        while (areas.hasNext()) {
            area = (WGArea) areas.next();
            areasMap.put(area.getName(), area);
        }

        return new WGAreaMap(areasMap);
    }

    /**
     * Returns a struct entry by it's struct key
     *
     * @param structKey
     *            The struct key to find a struct entry for.
     * @return WGStructEntry The found struct entry, null if there is none with
     *         that key.
     * @throws WGAPIException
     */
    public WGStructEntry getStructEntryByKey(Object structKey) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return (WGStructEntry) getDocumentByKey(new WGDocumentKey(WGDocument.TYPE_STRUCTENTRY, String.valueOf(structKey), null));
    }

    private WGStructEntry retrieveStructEntry(Object structKey) throws WGAPIException {

        WGStructEntry entry = null;

        WGDocumentCore entryCore = this.getCore().getStructEntryByKey(structKey);
        if (entryCore != null && !entryCore.isDeleted()) {
            entry = this.getOrCreateStructEntryObject(entryCore);
        }
        else {
            mapNonExistentDocIndicator(masterDocumentsByKey, new WGDocumentKey(WGDocument.TYPE_STRUCTENTRY, String.valueOf(structKey), null), WGDocument.TYPE_STRUCTENTRY);
        }

        return entry;
    }

    /**
     * Wraps a struct entry core into a WGStructEntry object
     *
     * @param doc
     *            core to wrap
     * @return WGStructEntry
     * @throws WGAPIException
     */
    protected WGStructEntry getOrCreateStructEntryObject(WGDocumentCore doc) throws WGAPIException {

        WGDocumentKey key = WGDocument.buildDocumentKey(doc, this);
        WGStructEntry entry;
        try {
            entry = (WGStructEntry) getDocumentByDocumentKeyFromCache(key, false);
        }
        catch (WGDocumentDoesNotExistException e) {
            entry = null;
        }

        if (entry == null) {
            entry = new WGStructEntry(this, doc);
            if (entry.getStructKey() != null) {

                // Directly fill some very relevant caches once we have the core
                if (!entry.isTemporary() && !entry.isDummy() && !"true".equals(getSessionContext().getAttribute(WGSessionContext.SATTRIB_BYPASS_PREEMTIVE_CACHING))) {
                    entry.getMetaData(WGStructEntry.META_KEY);
                    entry.getMetaData(WGStructEntry.META_AREA);
                    entry.getMetaData(WGStructEntry.META_POSITION);
                    entry.getMetaData(WGStructEntry.META_CONTENTTYPE);
                }

                mapDocumentObject(entry);
            }
        }
        else {
            if (!entry.isEdited()) {
                entry.setCore(doc);
            }
            // Make the released contents of the struct appear in the user's private content
            // cache
            if (getSessionContext().isCacheWritingEnabled()) {
                mapStructEntryObject(entry, false);
            }
        }
        return entry;

    }

    /**
     * Returns the child entries of a given struct entry
     *
     * @param structEntry
     *            struct entry to retrieve child entries for
     * @return WGAPIException
     */
    protected List getChildEntries(WGStructEntry structEntry) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (structEntry == null || structEntry.isDeleted() || structEntry.isDummy() || (structEntry.getCore() instanceof WGFakeDocument && structEntry.getCore().isDeleted())) {
            return new ArrayList();
        }

        Iterator structCores = this.getCore().getChildEntries(structEntry).iterator();

        ArrayList structs = new ArrayList();
        WGDocumentCore structCore;
        WGStructEntry entry;

        while (structCores.hasNext()) {
            structCore = (WGDocumentCore) structCores.next();
            entry = getOrCreateStructEntryObject(structCore);
            if (entry != null) {
                setStructParent(entry, structEntry);
                structs.add(entry);
            }
            else {
                WGFactory.getLogger().error("Could not retrieve struct entry " + structCore.getMetaData(WGStructEntry.META_KEY));
            }
        }

        // remove null elements
        while (structs.contains(null)) {
            structs.remove(null);
        }

        // sort entries
        Collections.sort(structs);

        return structs;
    }

    /**
     * Returns all content for a struct entry
     *
     * @param structEntry
     *            Struct entry, whose content is to be retrieved
     * @return WGContentList
     * @throws WGAPIException
     */
    protected WGContentList getAllContent(WGStructEntry structEntry, boolean includeArchived) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (structEntry == null || structEntry.isDeleted() || structEntry.isDummy() || (structEntry.getCore() instanceof WGFakeDocument && structEntry.getCore().isDeleted())) {
            return WGContentList.create();
        }

        ArrayList contents = new ArrayList();
        List contentCoresList = this.getCore().getAllContent(structEntry, includeArchived);
        if (contentCoresList != null) {
            Iterator contentCores = contentCoresList.iterator();

            WGDocumentCore contentCore;
            WGContent content;

            while (contentCores.hasNext()) {
                contentCore = (WGDocumentCore) contentCores.next();
                content = getOrCreateContentObject(contentCore);

                if (content != null) {
                    contents.add(content);
                }
            }
        }

        return WGContentList.create(contents);

    }
   
   /**
    * Returns the released content of the given struct entry and language without retrieving anything other
    *
    * @param entry The struct entry
    * @param strLanguage The language to retrieve.
    * @throws WGAPIException
    */
   public WGContent getReleasedContent(WGStructEntry entry, String strLanguage) throws WGAPIException {
      
       if (strLanguage == null) {
           strLanguage = getDefaultLanguage();
       }
      
       WGContentKey relContentKey = new WGContentKey(entry.getStructKey(), strLanguage, 0);
       return retrieveContentByKey(relContentKey);
      
      
   }
   

    /**
     * Returns on operation key for syncing of backend access. Should not be
     * used outside the WGAPI.
     *
     * @param operation
     * @param key
     * @return The key for the given operation. Can be a new created one or an
     *         existing key for this operation, if there was one.
     */
    public WGOperationKey obtainOperationKey(int operation, String key) {

        verboseBackendAccess(operation, key);

        String operationKeyStr = WGOperationKey.createString(operation, key);
        WGOperationKey opKey = (WGOperationKey) this.operations.get(operationKeyStr);
        if (opKey != null) {
            return opKey;
        }
        else {
            opKey = new WGOperationKey(operation, key);
            this.operations.put(operationKeyStr, opKey);
            return opKey;
        }
    }

    /**
     * Returns all content for a struct entry
     *
     * @param structEntry
     *            Struct entry, whose content is to be retrieved
     * @return WGContentList
     * @throws WGAPIException
     */
    protected WGContentList getAllContent(WGStructEntry structEntry) throws WGAPIException {
        return this.getAllContent(structEntry, false);
    }

    /**
     * Wraps a design document core into a matching WGDesignDocument object
     *
     * @param doc
     *            core to wrap
     * @return WGDesignDocument
     * @throws WGAPIException
     */
    private WGDesignDocument createDesignDocumentObject(WGDocumentCore doc) throws WGAPIException {

        int type = doc.getType();
        WGDesignDocument design = null;

        if (type == WGDocument.TYPE_AREA) {
            design = new WGArea(this, doc);
        }
        else if (type == WGDocument.TYPE_CONTENTTYPE) {
            design = new WGContentType(this, doc);
        }
        else if (type == WGDocument.TYPE_TML) {
            design = new WGTMLModule(this, doc);
        }
        else if (type == WGDocument.TYPE_FILECONTAINER) {
            design = new WGFileContainer(this, doc);
        }
        else if (type == WGDocument.TYPE_LANGUAGE) {
            design = new WGLanguage(this, doc);
        }
        else if (type == WGDocument.TYPE_CSSJS) {
            design = new WGScriptModule(this, doc);
        }

        mapDocumentObject(design);
        return design;
    }

    /**
     * Returns a design object with the given information
     *
     * @param type
     *            document type choosing, which kind of design object to
     *            retrieve. Use constants WGDocument.TYPE_...
     * @param strName
     *            Name of the design object
     * @param mediaKey
     *            media key of the design object. If design object has no media
     *            key, provide null.
     * @return WGDesignDocument
     * @throws WGAPIException
     */
    public WGDesignDocument getDesignObject(int type, String strName, String mediaKey) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (strName == null) {
            return null;
        }

        return (WGDesignDocument) getDocumentByKey(new WGDocumentKey(type, strName, mediaKey));
    }

    private WGDesignDocument retrieveDesignDocument(int type, String strName, String mediaKey) throws WGAPIException {

        WGDesignDocument design = null;
        WGDocumentCore designCore = getDesignObjectCore(type, strName, mediaKey);
        if (designCore != null && !designCore.isDeleted()) {
            design = this.createDesignDocumentObject(designCore);
        }
        else if (getSessionContext().isCacheWritingEnabled()) {
            mapNonExistentDocIndicator(masterDocumentsByKey, new WGDocumentKey(type, strName, mediaKey), type);
        }

        return design;

    }

    protected WGDocumentCore getDesignObjectCore(int type, String strName, String mediaKey) throws WGAPIException {
        String mediaKeyParam = (mediaKey != null ? mediaKey.toLowerCase() : null);
        String designNameParam = toLowerCaseMeta(strName);

        if (designProvider != null && designProvider.providesType(type) && !isMetadataModule(type, strName)) {
            return designProvider.getDesignObject(type, designNameParam, mediaKeyParam);
        }
        else {
            return this.getCore().getDesignObject(type, designNameParam, mediaKeyParam);
        }
    }

    private boolean isMetadataModule(int type, String strName) {

        return (type == WGDocument.TYPE_CSSJS && strName.startsWith(WGCSSJSModule.METADATA_MODULE_QUALIFIER));

    }

    /**
     * Returns a design object with the given information. In this method the
     * media key is omitted. Therefor it is not suitable to retrieve WebTML
     * Modules
     *
     * @param type
     *            document type choosing, which kind of design object to
     *            retrieve. Use constants WGDocument.TYPE_...
     * @param strName
     *            Name of the design object
     * @return WGDesignDocument
     * @throws WGAPIException
     */
    public WGDesignDocument getDesignObject(int type, String strName) throws WGAPIException {
        return this.getDesignObject(type, strName, null);
    }

    /**
     * Returns the root entries to an area.
     *
     * @param area
     *            The area to fetch root entries for
     * @return WGStructEntryList
     * @throws WGAPIException
     * @throws WGAPIException
     */
    protected List getRootEntries(WGArea area) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (area == null || area.isDeleted() || area.isDummy() || (area.getCore() instanceof WGFakeDocument && area.getCore().isDeleted())) {
            return new ArrayList();
        }

        Iterator rootCores = this.getCore().getRootEntries(area).iterator();

        ArrayList roots = new ArrayList();
        WGDocumentCore rootCore;
        WGStructEntry root;

        while (rootCores.hasNext()) {
            rootCore = (WGDocumentCore) rootCores.next();
            root = getOrCreateStructEntryObject(rootCore);
            if (root != null) {
                setStructParent(root, area);
                roots.add(root);
            }
        }

        // remove null elements
        while (roots.contains(null)) {
            roots.remove(null);
        }

        // sort root entries
        Collections.sort(roots);

        return roots;
    }

    /**
     * Returns the title of this database (if there is one).
     *
     * @return String
     */
    public String getTitle() {

        return title;

    }

    /**
     * Registers a listerer for database events.
     *
     * @param listener
     *            The listener to register.
     */
    public void addDatabaseEventListener(WGDatabaseEventListener listener) {

        if (listener.isTemporary()) {
            this.getSessionContext().addTemporaryEventListener(listener);
        }
        else {
            if (!this.databaseEventListeners.contains(listener)) {
                this.databaseEventListeners.add(listener);
            }
        }

    }

    /**
     * Adds a listener for the connection of this database to its backend.
     *
     * @param listener
     */
    public void addDatabaseConnectListener(WGDatabaseConnectListener listener) {

        if (!this.databaseConnectListeners.contains(listener)) {
            this.databaseConnectListeners.add(listener);
        }

    }

    /**
     * Removes a listener for the connection of this database to its backend
     *
     * @param listener
     */
    public void removeDatabaseConnectListener(WGDatabaseConnectListener listener) {
        this.databaseConnectListeners.remove(listener);
    }

    /**
     * Adds a listener for content events to the database.
     *
     * @param listener
     */
    public void addContentEventListener(WGContentEventListener listener) {

        if (!contentEventListeners.contains(listener)) {
            this.contentEventListeners.add(listener);
        }

    }

    /**
     * Adds a listener for workflow events to the database
     *
     * @param listener
     */
    public void addWorkflowEventListener(WGWorkflowEventListener listener) {

        if (!this.workflowEventListeners.contains(listener)) {
            this.workflowEventListeners.add(listener);
        }

    }

    /**
     * Removes a database event listener.
     */
    public void removeDatabaseEventListener(WGDatabaseEventListener listener) {

        if (listener.isTemporary()) {
            return;
        }

        this.databaseEventListeners.remove(listener);

    }

    /**
     * Removes a content event listener.
     */
    public void removeContentEventListener(WGContentEventListener listener) {

        this.contentEventListeners.remove(listener);

    }

    /**
     * Removes a workflow event listener.
     */
    public void removeWorkflowEventListener(WGWorkflowEventListener listener) {

        this.workflowEventListeners.remove(listener);

    }

    /**
     * Fires a database event and notifies all listeners
     *
     * @param event
     *            The event to throw
     */
    private synchronized void fireDatabaseEvent(WGDatabaseEvent event) {

        if (!isSessionOpen()) {
            return;
        }

        if (!getSessionContext().isEventsEnabled()) {
            return;
        }

        synchronized (databaseEventListeners) {
            Iterator listeners = this.databaseEventListeners.iterator();
            while (listeners.hasNext()) {
                WGDatabaseEventListener listener = (WGDatabaseEventListener) listeners.next();
                listener.databaseUpdate(event);
            }
        }

    }

    /**
     * Fires a event, that signals that this database has been connected to its
     * backend or that this process resulted in an error.
     *
     * @param event
     *            The event to fire
     */
    private synchronized void fireDatabaseConnectEvent(WGDatabaseEvent event) {

        if (!isSessionOpen()) {
            if (event.getType() != WGDatabaseEvent.TYPE_CONNECTION_ERROR) {
                return;
            }
        }
        else if (!getSessionContext().isEventsEnabled()) {
            return;
        }

        synchronized (databaseConnectListeners) {
            Iterator listeners = this.databaseConnectListeners.iterator();
            while (listeners.hasNext()) {
                WGDatabaseConnectListener listener = (WGDatabaseConnectListener) listeners.next();
                if (event.getType() == WGDatabaseEvent.TYPE_CONNECTED) {
                    listener.databaseConnected(event);
                }
                else if (event.getType() == WGDatabaseEvent.TYPE_CONNECTION_ERROR) {
                    listener.databaseConnectionError(event);
                }
            }
        }
    }

    protected synchronized boolean fireContentEvent(WGContentEvent event) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (!getSessionContext().isEventsEnabled()) {
            return true;
        }
       
        if (getSessionContext().isTransactionActive()) {
            return true;
        }

        synchronized (contentEventListeners) {
            Iterator listeners = this.contentEventListeners.iterator();
            while (listeners.hasNext()) {
                WGContentEventListener listener = (WGContentEventListener) listeners.next();
                if (event.getType() == WGContentEvent.TYPE_CREATED) {
                    listener.contentCreated(event);
                }
                else if (event.getType() == WGContentEvent.TYPE_SAVED) {
                    if (listener.contentSaved(event) == false) {
                        return false;
                    }
                }
                else if (event.getType() == WGContentEvent.TYPE_HASBEENSAVED) {
                    listener.contentHasBeenSaved(event);
                }
                else if (event.getType() == WGContentEvent.TYPE_HASBEENDELETED) {
                    listener.contentHasBeenDeleted(event);
                }
                else if (event.getType() == WGContentEvent.TYPE_HASBEENMOVED) {
                    listener.contentHasBeenMoved(event);
                }
                else if (event.getType() == WGContentEvent.TYPE_STATUSCHANGED) {
                    listener.contentStatusChanged(event);
                }
            }
        }
        return true;
    }

    /**
     * Fires a workflow event. Not to be called outside the WGAPI.
     *
     * @param event
     * @return A list of results that were returned by the event recipients
     */
    public synchronized List fireWorkflowEvent(WGWorkflowEvent event) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (!getSessionContext().isEventsEnabled()) {
            return new ArrayList();
        }

        List resultList = new ArrayList();
        synchronized (workflowEventListeners) {
            Iterator listeners = this.workflowEventListeners.iterator();
            while (listeners.hasNext()) {
                WGWorkflowEventListener listener = (WGWorkflowEventListener) listeners.next();
                listener.workflowMail(event);
            }
        }
        return resultList;
    }

    /**
     * Opens a new session.
     *
     * @param user
     *            The username for the new session. Specify null for master
     *            login.
     * @param credentials
     *            The password to the username. Specify null for master login.
     * @return The access level to the database, as constant
     *         WGDatabase.ACCESSLEVEL_...
     * @throws WGAPIException
     */
    public int openSession(String user, Object credentials) throws WGAPIException {
       
        /* Do we really want to enforce certificate auth in all cases if enabled? This would not allow login via non-browser clients
        if (certAuthEnabled() && (user != null || credentials != null)) {
            WGFactory.getLogger().warn("WGAPI: Tried to access database " + getDbReference() + " via username/login although it is configured for certificate authentication");
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }*/
       
        return innerOpenSession(user, credentials);
       
    }
   
    private int innerOpenSession(String user, Object credentials) throws WGAPIException {

        if (!isReady()) {
            throw new WGUnavailableException(this, "The database either has been closed or not yet been opened");
        }
       
        if (this.isSessionOpen()) {
            WGFactory.getLogger().warn("WGAPI: Tried to open an already opened session on database '" + getTitle() + "' (" + getTypeName() + ")");
            return this.getSessionContext().getAccessLevel();
        }

        // Determine if this database yet has to be connected to the backend
        if (!isConnected()) {
            boolean wasConnected = false;
            try {
                wasConnected = connectDBCore(false);
            }
            catch (WGInvalidDatabaseException e) {
                WGDatabaseEvent event = new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_CONNECTION_ERROR);
                fireDatabaseConnectEvent(event);
                throw new WGUnavailableException(this, "Unable to connect to database core", e);
            }

            // Fire the connect event in case connectDBCore has connected the
            // core
            // If this is false, the current thread most likely was blocked on
            // connectDBCore() by some other
            // thread that built the core connection.So this thread entered the
            // method when the core was
            // already connected.
            if (wasConnected == true) {
                WGDatabaseEvent event = new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_CONNECTED);
                fireDatabaseConnectEvent(event);
                notifyConnectActions();
                // We close the (master) session that resulted from connecting
                // the core
                // We will open the real user session later
                closeSession();
            }
        }

        WGFactory.getLogger().debug("WGDatabase open session " + path);

        // If both params are null, the master login (from initial open command)
        // will be used
        boolean masterLogin = false;
       
        // Create authentication session
        AuthenticationSession authSession = null;
        AuthenticationModule authModule = getAuthenticationModule();

        // Master login
        if (user == null && credentials == null) {
            user = this.masterLoginInputName;
            credentials = this.masterLoginPassword;
            masterLogin = true;
            authSession = MasterLoginAuthSession.getInstance();
        }
       
        // Anonymous login
        else if (user.equals(WGDatabase.ANONYMOUS_USER)) {
            authSession = AnonymousAuthSession.getInstance();
        }
       
        // Regular login against authentication module
       
        else if (authModule != null) {
            if (certAuthEnabled() && (credentials instanceof X509Certificate)) {
                authSession = ((CertAuthCapableAuthModule) authModule).login((X509Certificate) credentials);
            }
            else {
                authSession = authModule.login(user, credentials);
            }
           
            if (authSession == null) {
                return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
            }
        }
       
        // Backend login
        else if (hasFeature(FEATURE_PERFORMS_BACKEND_LOGIN)) {
            authSession = new BackendAuthSession(user, credentials);
        }
       
        // If no auth module and database does not accept backend logins we use an anonymous session
        else {
            authSession = AnonymousAuthSession.getInstance();
        }

        // Open core session
        WGUserAccess userAccess = this.core.openSession(authSession, credentials, masterLogin);
        if (userAccess.getAccessLevel() <= ACCESSLEVEL_NOACCESS) {
            return userAccess.getAccessLevel();
        }

        userHashMapGroup.fetchAllMapsForUser(authSession.getDistinguishedName());
        WGSessionContext sessionContext = new WGSessionContext(this, authSession, credentials, userAccess);
        sessionContext.setCachingEnabled(cachingEnabled);
        this.setSessionContext(sessionContext);
        core.setCurrentSession(sessionContext);
       
        // Notify design provider
        if (designProvider != null) {
            designProvider.openSession(sessionContext);
        }
      
        return userAccess.getAccessLevel();

    }
   
    /**
     * Opens a new session using a certificate for login.
     * To use certificate authentication you must use a {@link CertAuthCapableAuthModule} with enabled certificate authentication
     * @param cert
     *            The certificate used to login.
     * @return The access level to the database, as constant
     *         WGDatabase.ACCESSLEVEL_...
     * @throws WGAPIException
     */
    public int openSession(X509Certificate cert) throws WGAPIException {
       
        if (!certAuthEnabled()) {
            WGFactory.getLogger().warn("WGAPI: Tried to access database " + getDbReference() + " via certificate auth although it is not configured for it");
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }
       
        if (cert == null) {
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }
       
        CertAuthCapableAuthModule auth = (CertAuthCapableAuthModule) getAuthenticationModule();
       
        X509Certificate caCert = auth.getCA();
        X509CRL crl = auth.getCRL();
       
        // verify if clientCert is issued by given dbCA
        if (!CertificateValidationUtils.verify(cert, caCert)) {
            String message = "Failed login for '" + cert.getSubjectDN().getName() + "': Certificate was signed by another CA (Certificate authentication)";           
            WGFactory.getLogger().warn(message);
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }
       
        // check if client certificate is revoked
        if (CertificateValidationUtils.isCertRevoked(crl, cert)) {
            String message = "Failed login for '" + cert.getSubjectDN().getName() + "': Certificate was revoked by CRL (Certificate authentication)";
            WGFactory.getLogger().info(message);
            return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
        }
       
        String user = cert.getSubjectDN().toString();
        return innerOpenSession(user, cert);
       
    }

    private synchronized void notifyConnectActions() {

        Iterator actions = _connectActions.iterator();
        while (actions.hasNext()) {
            ConnectAction action = (ConnectAction) actions.next();
            try {
                action.run(this);
            }
            catch (Exception e) {
                WGFactory.getLogger().error("Error executing connect action", e);
            }
        }
        _connectActions.clear();

    }

    /**
     * Opens a new session with master login.
     *
     * @throws WGAPIException
     */
    public int openSession() throws WGAPIException {
        return this.openSession(null, null);
    }

    private List wrapDesignCores(List designCores) throws WGAPIException {

        if (designCores == null) {
            return null;
        }

        if (designCores.size() == 0) {
            designCores = new ArrayList();
        }

        List designs = new ArrayList();
        Iterator objectCores = designCores.iterator();

        WGDocumentCore designCore;
        WGDesignDocument design;
        String name;
        String mediaKey;

        while (objectCores.hasNext()) {
            designCore = (WGDocumentCore) objectCores.next();
            if (designCore == null || designCore.isDeleted()) {
                continue;
            }

            name = (String) designCore.getMetaData(WGDesignDocument.META_NAME);

            if (designCore.getType() == WGDocument.TYPE_TML) {
                mediaKey = (String) designCore.getMetaData(WGTMLModule.META_MEDIAKEY);
            }
            else {
                mediaKey = null;
            }

            design = getOrCreateDesignDocumentObject(designCore);
            if (design != null) {
                designs.add(design);
            }
            else {
                if (WGFactory.getLogger().isDebugEnabled()) {

                    WGFactory.getLogger().error(
                            "Could not retrieve design object " + WGDesignDocument.doctypeNumberToName(designCore.getType()) + "/" + name + "/" + mediaKey + ". Check name validity");
                }
            }
        }
        return designs;
    }

    /**
     * Returns the imlementation type (i.e. the class name of the database core
     * implementation)
     */
    public String getType() {

        return this.type;

    }

    /**
     * Returns a descriptive type name of the database implementation used.
     */
    public String getTypeName() {
        return typeName;
    }

    /**
     * Returns all design objects of a specific type.
     *
     * @param type
     *            The type of design objects to retrieve. Use constants
     *            WGDocument.TYPE_...
     * @return ArrayList List of WGDesignObject objects
     * @throws WGAPIException
     */
    public List getDesignObjects(int type) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        // Double checked cache (un-synchronized and synchronized)
        Integer typeObj = new Integer(type);
        List designs = fetchDocumentListCache((WGDocumentListCache) this.designDocumentLists.get(typeObj), type);
        if (!getSessionContext().isCachingEnabled() || designs == null) {
            WGOperationKey op = obtainOperationKey(WGOperationKey.OP_DESIGN_LIST, String.valueOf(type));
            synchronized (op) {
                try {
                    op.setUsed(true);
                    designs = fetchDocumentListCache((WGDocumentListCache) this.designDocumentLists.get(typeObj), type);
                    if (!getSessionContext().isCachingEnabled() || designs == null) {
                        designs = wrapDesignCores(getDesignObjectCores(type));
                        if (getSessionContext().isCacheWritingEnabled()) {
                            this.designDocumentLists.put(typeObj, WGDocumentListCache.buildFromDocuments(designs));
                        }
                    }
                }
                finally {
                    op.setUsed(false);
                }
            }
        }

        if (designs != null) {
            return designs;
        }
        else {
            return null;
        }

    }

    protected List fetchDocumentListCache(WGDocumentListCache listCache, int docType) {

        if (!isDoctypeCacheable(docType)) {
            return null;
        }

        if (listCache != null && isCachingEnabled()) {
            return listCache.buildDocumentList(this);
        }
        else {
            return null;
        }
    }

    protected boolean isDoctypeCacheable(int docType) {
        boolean docTypeCacheable = true;
        if (getDesignProvider() != null) {
            if (!getDesignProvider().isNotifying() && getDesignProvider().providesType(docType)) {
                docTypeCacheable = false;
            }
        }
        return docTypeCacheable;
    }

    private List getDesignObjectCores(int type) throws WGAPIException {

        if (designProvider != null && designProvider.providesType(type)) {
            List designCores = designProvider.getDesignObjects(type);
            if (designCores != null && type == WGDocument.TYPE_CSSJS) {
                designCores = removeMetadataModules(designCores);
            }
            return designCores;
        }
        else {
            return this.getCore().getDesignObjects(type);
        }

    }

    private List removeMetadataModules(List designCores) throws WGAPIException {

        List results = new ArrayList();
        Iterator cores = designCores.iterator();
        WGDocumentCore core;
        while (cores.hasNext()) {
            core = (WGDocumentCore) cores.next();
            if (!isMetadataModule(core.getType(), (String) core.getMetaData(WGDesignDocument.META_NAME))) {
                results.add(core);
            }
        }
        return results;

    }

    /**
     * Retrieves a content by it's unique name.
     *
     * @param strName
     *            Unique name of the content
     * @param strLanguage
     *            Language of the content
     * @return WGContent Content matching unique name and language, null if none
     *         exists.
     * @throws WGAPIException
     */
    public WGContent getContentByName(String strName, String strLanguage) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (strName == null || strName.trim().equals("")) {
            return null;
        }
        strName = strName.toLowerCase();

        String strCacheLanguage = strLanguage;
        if (strLanguage == null) {
            strCacheLanguage = getDefaultLanguage();
            if (strCacheLanguage == null) {
                strCacheLanguage = "de";
            }
        }

        strCacheLanguage = strCacheLanguage.toLowerCase();

        // Double checked cache (un-synchronized and synchronized)
        try {
            String byNameKey = strName.concat("|").concat(strCacheLanguage);
            WGContent content = (WGContent) fetchDocumentFromCache(this.contentByName, byNameKey);
            if (content == null) {
                WGOperationKey op = obtainOperationKey(WGOperationKey.OP_CONTENT_BY_NAME, strName);
                synchronized (op) {
                    try {
                        op.setUsed(true);
                        content = (WGContent) fetchDocumentFromCache(this.contentByName, byNameKey);
                        if (content == null) {
                            content = fetchContentByName(strName, strLanguage, byNameKey);
                        }
                    }
                    finally {
                        op.setUsed(false);
                    }
                }
            }

            // Look if unique name has changed
            if (content != null && !(strName.equals(content.getUniqueName()) || strName.equals(content.getStructEntry().getUniqueName())) ) {
                this.contentByName.remove(byNameKey);
                content = null;
            }

            return content;
        }
        catch (WGDocumentDoesNotExistException e) {
            return null;
        }

    }

    private WGContent fetchContentByName(String strName, String strLanguage, String byNameKey) throws WGAPIException {
        WGContent content = null;
       
        // First try to find a struct entry and see if there is a released content in correct language (if cs version >= 5)
        if (getContentStoreVersion() >= CSVERSION_WGA5) {
            WGDocumentCore structCore = getCore().getStructEntryByName(strName);
            if (structCore != null) {
                WGStructEntry struct = getOrCreateStructEntryObject(structCore);
                content = struct.getReleasedContent(strLanguage);
                if (content != null) {
                    return content;
                }
            }
        }
       
        // The try to find a content with the uname/lang combination
        WGDocumentCore contentCore = getCore().getContentByName(strName, strLanguage);
        if (contentCore != null && !contentCore.isDeleted()) {
            content = getOrCreateContentObject(contentCore);
            if (content != null) {
                return content;
            }
        }
       
        // No content to find, cache the "non-existence" then return null
        mapNonExistentDocIndicator(contentByName, byNameKey, WGDocument.TYPE_CONTENT);
        return null;
       
       
    }

    /**
     * Retrieves a content by it's unique name in the database's default
     * language.
     *
     * @param name
     *            Unique name of the content
     * @return WGContent Content matching unique name, null if none exists.
     * @throws WGAPIException
     */
    public WGContent getContentByName(String name) throws WGAPIException {
        return getContentByName(name, getDefaultLanguage());
    }

    /**
     * Sets the sessionContext
     *
     * @param sessionContext
     *            The sessionContext to set
     */
    private void setSessionContext(WGSessionContext sessionContext) {
        this.sessionContext.set(sessionContext);
    }

    /**
     * Sets a custom attribute to the database. These attributes remain at the
     * database object forever and can even be retrieved when no session is
     * open. The attributes can later be retrieved via getAttribute().
     *
     * @param name
     *            The custom attribute name
     * @param obj
     *            The attribute value.
     */
    public void setAttribute(String name, Object obj) {
        if (obj != null) {
            this.customAttributes.put(name, obj);
        }
        else {
            this.customAttributes.remove(name);
        }
    }

    /**
     * Removes a database attribute.
     *
     * @param name
     *            Name of the attribute to remove
     * @return The removed attribute value, if there was one, null otherwise
     */
    public Object removeAttribute(String name) {
        return this.customAttributes.remove(name);
    }

    /**
     * Returns a custom attribute set to this database.
     *
     * @param name
     *            Name of the attribute
     * @return Object Value of the attribute
     */
    public Object getAttribute(String name) {
        return this.customAttributes.get(name);
    }

    /**
     * Tests if the database contains an attribute of the given name
     *
     * @param name
     *            Attribute name
     * @return true, if attribute exists, false (suprise) if not
     */
    public boolean hasAttribute(String name) {
        return this.customAttributes.containsKey(name);
    }

    /**
     * Returns the boolean value of an attribute, accepting boolean objects and
     * those string representations that are converted by
     * WGUtils.stringToBoolean().
     *
     * @param name
     *            The name of the Attribute
     * @param defaultValue
     *            The default value to return, if the attribute is not
     *            interpretable as boolean
     * @return The boolean value of the attribute, or the default value if the
     *         attribute was not present or it's boolean value was not
     *         determinable
     */
    public boolean getBooleanAttribute(String name, boolean defaultValue) {
        return WGUtils.getBooleanMapValue(customAttributes, name, defaultValue);
    }

    /**
     * Executes a query in the database giving a result set of content documents
     * in return.
     *
     * @param type
     *            The type of query. Available types vary by database
     *            implementation.
     * @param query
     *            The query. Format depends on query type.
     * @param parameters
     *            Additional parameters for the query. Use Constants
     *            WGDatabase.QUERYOPTION_... as map keys.
     * @return A result set containing the contents matching the query
     * @throws WGAPIException
     */
    public WGAbstractResultSet query(String type, String query, Map parameters) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (parameters == null) {
            parameters = new HashMap();
        }

        Boolean cachedResultSet = (Boolean) parameters.get(WGDatabase.QUERYOPTION_CACHERESULT);
        if (cachedResultSet == null) {
            cachedResultSet = new Boolean(false);
        }

        QueryCacheKey queryCacheKey = new QueryCacheKey(type, query, new HashMap(parameters));
        if (cachedResultSet.booleanValue() == true) {
            QueryCacheEntry cacheEntry = (QueryCacheEntry) queryCache.get(queryCacheKey);
            if (cacheEntry != null) {
                parameters.put(WGDatabase.QUERYOPTION_RETURNQUERY, cacheEntry.getFullQuery());
                parameters.put(WGDatabase.QUERYOPTION_USEDCACHE, new Boolean(true));
                return new WGStandardResultSet(this, cacheEntry.getResultSet(), parameters);
            }
        }

        verboseBackendAccess(WGOperationKey.OP_QUERY, query);
        WGResultSetCore resultSetCore = this.core.query(type, query, parameters);

        if (!parameters.containsKey(WGDatabase.QUERYOPTION_RETURNQUERY)) {
            parameters.put(WGDatabase.QUERYOPTION_RETURNQUERY, query);
        }

        if (cachedResultSet.booleanValue() == true && getSessionContext().isCacheWritingEnabled()) {
            WGCachedResultSet resultSet = new WGCachedResultSet(resultSetCore);
            queryCache.put(queryCacheKey, new QueryCacheEntry(resultSet, (String) parameters.get(WGDatabase.QUERYOPTION_RETURNQUERY)));
            return new WGStandardResultSet(this, resultSet, parameters);
        }
        else {
            return new WGStandardResultSet(this, resultSetCore, parameters);
        }

    }

    /**
     * Queries the database for specific features.
     *
     * @param featureName
     *            The feature to query. Use Constants WGDatabase.FEATURE_...
     * @return true if the database implementation supports this feature, false
     *         otherwise
     */
    public boolean hasFeature(String featureName) {

        Boolean feature = (Boolean) this.features.get(featureName);
        if (feature == null) {
           
            if (featureName.equals(WGDatabase.FEATURE_SESSIONTOKEN)) {
                feature = (_authenticationModule != null && _authenticationModule.isGeneratesSessionToken());
            }
            else {
                feature = new Boolean(this.getCore().hasFeature(featureName));
            }
            this.features.put(featureName, feature);
        }

        return feature.booleanValue();
    }

    /**
     * Returns the roles of this db implementation, describing what documents
     * can be found in it.
     *
     * @return List List of constants WGDatabase.ROLE_... <br/>
     *         WGDatabase.ROLE_CONTENT: Database contains content documents and
     *         related (WGContent, WGStructEntry, WGArea) <br/>
     *         WGDatabase.ROLE_DESIGN: Database contains design documents
     *         (WGTMLModule, WGCSSJSLibrary) <br/>
     *         WGDatabase.ROLE_REPOSITORY: Database contains file containers
     *         (WGFileContainer) <br/>
     *         WGDatabase.ROLE_USERPROFILES: Database contains user profiles
     *         (WGUserProfile) <br/>
     */
    public List getRoles() {
        return this.getCore().getRoles();
    }

    /**
     * Wraps a content core into a WGContent object
     *
     * @param doc
     *            content core to wrap
     * @return WGContent
     * @throws WGAPIException
     */
    protected WGContent createContentObject(WGDocumentCore doc) throws WGAPIException {

        WGContent content = new WGContent(this, doc);
        mapDocumentObject(content);
        return content;

    }

    /**
     * Maps the document object on diverse document caches at the database For
     * content objects this method should only be called for new and modified
     * documents as it will remove the content from all user's private caches
     *
     * @param doc
     * @throws WGAPIException
     */
    private void mapDocumentObject(WGDocument doc) throws WGAPIException {

        if (doc == null || doc.isTemporary() || doc.isDummy()) {
            return;
        }
       
        if (!isDoctypeCacheable(doc.getType())) {
            return;
        }

        WGDocumentKey documentKey = doc.getDocumentKeyObj();
        updateCacheMap(masterDocumentsByKey, documentKey, doc);

        if (doc instanceof WGContent) {
            mapContentObject((WGContent) doc, true);
        }
        else if (doc instanceof WGStructEntry) {
            mapStructEntryObject((WGStructEntry) doc, true);
        }

    }

    private void mapStructEntryObject(WGStructEntry doc, boolean removeFromOtherUsers) throws WGAPIException {
       
        // If struct has a unique name we must map all existent content to the contentByName cache
        // REALLY? This spoils any optimization about language load behaviour. Lets try without since the mapping is also done for the content document.
        /*
        String uname = doc.getUniqueName();
        if (!WGUtils.isEmpty(uname)) {
            Iterator contents = doc.getContentSet(false).getReleasedContent().values().iterator();
            while (contents.hasNext()) {
                WGContent content= (WGContent) contents.next();
                mapUniqueName(uname, content, removeFromOtherUsers);
            }
        }*/
       
       
    }

    private void mapContentObject(WGContent content, boolean removeFromOtherUsers) throws WGAPIException {

        if (!isDoctypeCacheable(WGDocument.TYPE_CONTENT)) {
            return;
        }

        String contentKeyStr = content.getContentKey(true).toString();

        if (removeFromOtherUsers) {
            this.contentByKey.removeFromAllUsers(contentKeyStr);
        }
        updateCacheMap(this.contentByKey, contentKeyStr, content.getDocumentKeyObj());

        // Map uname from content
        String uniqueName = content.getUniqueName();
        if (!WGUtils.isEmpty(uniqueName)) {
            mapUniqueName(uniqueName, content, removeFromOtherUsers);
        }
       
        // Map uname from struct
        uniqueName = content.getStructEntry().getUniqueName();
        if (!WGUtils.isEmpty(uniqueName)) {
            mapUniqueName(uniqueName, content, removeFromOtherUsers);
        }
       

    }

    private void mapUniqueName(String uniqueName, WGContent content, boolean removeFromOtherUsers) throws WGAPIException {
        String uniqueNameKey = uniqueName.toLowerCase().concat("|").concat(content.getLanguage().getName());
       
        if (content.getStatus().equals(WGContent.STATUS_RELEASE)) {
            if (removeFromOtherUsers) {
                this.contentByName.removeFromAllUsers(uniqueNameKey);
            }
           
            updateCacheMap(this.contentByName, uniqueNameKey, content.getDocumentKeyObj());
        }
        else if (removeFromOtherUsers) {
            this.contentByName.removeFromAllUsers(uniqueNameKey, content.getDocumentKeyObj());
        }
    }

    /**
     * Method to update cache maps with values. This method takes
     * {@link WGSessionContext#isCacheWritingEnabled()} into credit and refuses
     * to put the value in the cache if cache writing is disabled. In that case
     * it just clears the cache entry if the previous entry is not the same as
     * the entry to set.
     *
     * @param map
     * @param key
     * @param value
     */
    private void updateCacheMap(Map map, Object key, Object value) {

        // In case the correct object is already in cache we do nothing
        if (map.get(key) == value) {
            return;
        }

        if (getSessionContext().isCacheWritingEnabled()) {
            map.put(key, value);
        }
        else {
            map.remove(key);
        }

    }

    protected WGDocument unmapDocumentObject(WGDocument doc) throws WGAPIException {

        if (doc == null || doc.isTemporary()) {
            return null;
        }

        WGDocument cachedDocument = (WGDocument) masterDocumentsByKey.remove(doc.getDocumentKeyObj());

        if (doc instanceof WGContent) {
            WGContent content = (WGContent) doc;
            this.contentByKey.removeFromAllUsers(content.getContentKey(true).toString());
            if (content.getRetrievalStatus().equals(WGContent.STATUS_RELEASE)) {
                String uniqueName = content.getRetrievalUniqueName();
                if (uniqueName != null && !uniqueName.equals("")) {
                    this.contentByName.removeFromAllUsers(uniqueName.toLowerCase().concat("|").concat(content.getRetrievalLanguage()));
                }
            }
        }

        else if (doc instanceof WGDesignDocument) {
            WGDesignDocument design = (WGDesignDocument) doc;
            WGDocumentListCache designs = (WGDocumentListCache) this.designDocumentLists.get(new Integer(doc.getType()));
            if (designs != null) {
                designs.remove(doc);
            }
        }

        return cachedDocument;

    }

    /**
     * Creates a new content document. This version can determine the version to
     * use and is mainly for cloning content.
     *
     * @param entry
     *            struct Entry, that this new content document will be attached
     *            to
     * @param language
     *            Language of the new content
     * @param title
     *            Title of the new content
     * @param Integer
     *            version The needed version for the created document
     * @throws WGAPIException
     */
    protected synchronized WGContent createContent(WGStructEntry entry, WGLanguage language, String title, Integer version) throws WGAPIException {

        if (!this.isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && (entry == null || language == null || title == null)) {
            throw new WGIllegalArgumentException("Mandatory parameter (Struct entry, language or title) is empty");
        }

        performContentCreationCheck(entry, language);

        // Evaluate new version number
        int newVersion = 0;
        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            if (version != null) {
                newVersion = version.intValue();
            }
            else {
                newVersion = findNewVersionNumber(entry, language);
            }

        }

        // Create core and pre-initialize to be a valid WGContent object
        WGDocumentCore docCore = this.getCore().createContent(entry, language, title, newVersion);
        if (docCore == null) {
            throw new WGCreationException("Unable to create content. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of content");
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            docCore.setMetaData(WGContent.META_STATUS, WGContent.STATUS_DRAFT);
        }

        // Create WGContent object
        WGContent newContent = this.createContentObject(docCore);

        // Initialize
        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            newContent.setVisible(true);
        }
        newContent.setTitle(title);
        newContent.setMetaData(WGContent.META_AUTHOR, getSessionContext().getUser());
        if (getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            newContent.setMetaData(WGContent.META_OWNER, getSessionContext().getUser());
        }
       



        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
           
            // Create predefined item/meta fields based on schema definition
            WGContentTypeDefinition schemaDef = (WGContentTypeDefinition) entry.getContentType().getSchemaDefinition();
            if (schemaDef != null) {
                for (WGContentItemDefinition itemDef : schemaDef.getContentItemDefinitions()) {
                    if (itemDef.hasInitialValue()) {
                        newContent.setItemValue(itemDef.getName(), itemDef.getInitialValue());
                    }
                }
                for (WGMetaFieldDefinition metaDef : schemaDef.getContentMetaDefinitions()) {
                    newContent.setMetaData(metaDef.getName(), metaDef.getValues());
                }
            }

            // Add the newly created content key to prevent using it again (while
            // the content has not been saved)
            newContentKeys.put(newContent.getContentKey(), null);
           
            // Initialize by workflow engine
            WGWorkflow workflow = this.getWorkflowEngine().getWorkflow(newContent);
            workflow.initialize();
        }


        // Event contentCreated
        String contentType = null;
        if (entry != null && entry.getContentType() != null) {
            contentType = entry.getContentType().getName();
        }
        WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_CREATED, newContent.getDocumentKey(), contentType, this);
        event.setContent(newContent);
        fireContentEvent(event);

        // return
        return newContent;

    }

    /**
     * Controls if the current user may create a content document with the given data. If so the method exists normally. If not an exception is thrown.
     * @param entry The struct entry under which to create the content. If given null all struct entry data checks are bypassed.
     * @param language The language in which to create it
     * @throws WGAPIException If the user may not create the document. The exception informs about the reason.
     */
    public void performContentCreationCheck(WGStructEntry entry, WGLanguage language) throws WGAPIException, ResourceIsLockedException, WGAuthorisationException, WGIllegalStateException {
        // check entry lock
        if (entry != null && entry.getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create content. Given structentry is locked.");
        }

        if (getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_AUTHOR) {
            throw new WGAuthorisationException("You are not authorized to create content in this database");
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && entry != null && !entry.isSaved()) {
            throw new WGIllegalStateException("Struct entry for this content has not yet been saved");
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && !language.isSaved()) {
            throw new WGIllegalStateException("Language for this content has not yet been saved");
        }
      
        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && language.getDatabase() != this) {
            throw new WGIllegalDataException("The language definition is from a different database");
        }
      
        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && entry != null && entry.mayEditEntryAndContent() != null) {
            throw new WGAuthorisationException("User is not allowed to create content under this struct entry", entry);
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && entry != null) {
            WGContentType contentType = entry.getContentType();
            if (contentType != null && !contentType.mayCreateContent()) {
                throw new WGAuthorisationException("User is not allowed to use this content type");
            }
            else if (contentType == null && getSessionContext().isDesigner() == false) {
                throw new WGAuthorisationException("User is not allowed to use struct entries without content types");
            }
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && !language.mayCreateContent()) {
            throw new WGAuthorisationException("User is not allowed to create content in this language");
        }

        // Test if draft content allready exists for this language code in
        // status DRAFT
        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            if (!getSessionContext().isMasterSession() && entry != null && entry.hasContent(language.getName(), WGContent.STATUS_DRAFT)) {
                throw new WGIllegalStateException("There is already an existing draft copy in language " + language.getTitle() + ".");
            }
        }
    }

    /**
     * @param entry
     * @param language
     * @return
     */
    private int findNewVersionNumber(WGStructEntry entry, WGLanguage language) {

        FreeContentVersionFinder finder = new FreeContentVersionFinder(entry, language);
        return finder.findNewVersion();

    }

    /**
     * Creates a new content document. This method flavor is meant for
     * "complete" content stores with struct entry hierarchy.
     *
     * @param entry
     *            The struct entry for the new content
     * @param language
     *            The language of the new content
     * @param title
     *            The title of the new content
     * @return The newly created content document.
     * @throws WGAPIException
     */
    public synchronized WGContent createContent(WGStructEntry entry, WGLanguage language, String title) throws WGAPIException {
        return createContent(entry, language, title, null);
    }

    /**
     * Creates a simple content document with the content key given. This method
     * flavor is meant for content stores without struct entry hierarchy, like
     * those derived from the SimpleContentSource-Class
     *
     * @param area
     *            The area to create the content in.
     * @param key
     *            The key of the new content.
     * @param title
     *            The title of the new content.
     * @return The newly created content.
     * @throws WGAPIException
     */
    public synchronized WGContent createContent(WGArea area, Object key, String title) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            throw new WGNotSupportedException("This creation method is not available with this database implementation, because this implementation needs more information to create a content.");
        }

        if (area == null) {
            throw new WGIllegalArgumentException("There was no area provided");
        }

        WGStructEntry entry = createStructEntry(key, area, null, title);
        entry.save();

        return this.createContent(entry, null, title);

    }

    /**
     * Equivalent to calling createStructKey(null, parent, contentType, title).
     *
     * @param parent
     * @param contentType
     * @param title
     * @throws WGAPIException
     */
    public synchronized WGStructEntry createStructEntry(WGDocument parent, WGContentType contentType, String title) throws WGAPIException {
        return createStructEntry(null, parent, contentType, title);
    }

    /**
     * Creates a new struct entry
     *
     * @param key
     *            The struct key of the new entry. Can be specified when
     *            database supports feature
     *            WGDatabase.FEATURE_ACCEPTS_STRUCTKEYS. Can be left out with
     *            null, when the database supports feature
     *            WGDatabase.FEATURE_GENERATES_STRUCTKEYS.
     * @param parent
     *            Parent object. Can be a WGArea object (new entry will be root)
     *            or another WGStructEntry object (new entry will be child)
     * @param contentType
     *            Content type for contents of this struct entry
     * @param title
     *            Title of this struct entry
     * @return WGStructEntry
     * @throws WGAPIException
     */
    public synchronized WGStructEntry createStructEntry(Object key, WGDocument parent, WGContentType contentType, String title) throws WGAPIException {

        if (!this.isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (parent == null || title == null) {
            throw new WGIllegalArgumentException("Mandatory parameter parent or title is empty");
        }

        performStructCreationCheck(key, parent, contentType);

        WGDocumentCore entryCore = this.getCore().createStructEntry(key, parent, contentType);
        if (entryCore == null) {
            throw new WGCreationException("Unable to create struct entry. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of struct entries");
        }
        WGStructEntry newEntry = this.getOrCreateStructEntryObject(entryCore);

        // Initialize
        newEntry.setMetaData(WGStructEntry.META_TITLE, title);

        // DISABLED: Area is set inside DatabaseCore.createStructEntry
        // WGArea area = (parent instanceof WGArea ? (WGArea) parent :
        // ((WGStructEntry) parent).getArea());
        // newEntry.setMetaData(WGStructEntry.META_AREA,
        // (hasFeature(FEATURE_USE_OBJECTS_AS_REFERENCES) ? area.getCore() :
        // (Object) area.getName()));

        newEntry.setMetaData(WGStructEntry.META_POSITION, new Integer(0));
        if (contentType != null) {
            newEntry.setMetaData(WGStructEntry.META_CONTENTTYPE, (hasFeature(FEATURE_USE_OBJECTS_AS_REFERENCES) ? contentType.getCore() : (Object) contentType.getName()));
        }
        setStructParent(newEntry, parent);

        // Save and return
        // newEntry.save();
        return newEntry;

    }

    /**
     * Controls if the current user may create a struct entry with the given data. If so the method exists normally. If not an exception is thrown.
     * @param key A struct key that is to be determined for the new struct entry. Give null if you want the WGAPI to generate the key.
     * @param parent The parent of the new struct entry. May be an {@link WGArea} or another {@link WGStructEntry}.
     * @param contentType The content type of the new struct entry.
     * @throws WGAPIException If the user may not create the document. The exception informs about the reason.
     */
    public void performStructCreationCheck(Object key, WGDocument parent, WGContentType contentType) throws WGAPIException {

       
        if (parent == null) {
            throw new WGIllegalDataException("No parent document was given");
        }
       
        if (parent.getDatabase() != this) {
            throw new WGIllegalDataException("The parent document is from a different database");
        }
       
        // check parent lock
        if (parent.getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create structentry. Parent (structentry or area) is locked.");
        }

        if (!parent.isSaved()) {
            throw new WGIllegalStateException("Parent object of this struct entry (either struct entry or area) has not yet been saved");
        }

        if (hasFeature(WGDatabase.FEATURE_FULLCONTENTFEATURES) && contentType == null) {
            throw new WGAuthorisationException("You did not provide a content type which is mandatory for this database type");
        }

        if (contentType != null) {
           
            if (!contentType.isSaved()) {
            throw new WGIllegalStateException("Content type for this struct entry has not yet been saved");
        }
            if (contentType != null && !contentType.mayCreateChildEntry(parent)) {
                throw new WGAuthorisationException("User is not allowed to use this content type at this position");
            }
            if (contentType.getDatabase() != this) {
                throw new WGIllegalDataException("The content type is from a different database");
            }
           
        }

        if (!(parent instanceof WGArea || parent instanceof WGStructEntry)) {
            throw new WGIllegalArgumentException("parent parameter of invalid document type: " + (parent != null ? parent.getClass().getName() : "null"));
        }

        if (getSessionContext().getAccessLevel() < ACCESSLEVEL_AUTHOR) {
            throw new WGAuthorisationException("You are not authorized to create struct entries in this database");
        }

        boolean isRoot = (parent instanceof WGArea);
        if (isRoot) {
            if (!((WGArea) parent).mayEditAreaChildren()) {
                throw new WGAuthorisationException("User is not allowed to edit entries in this area");
            }
        }
        else {
            if (((WGStructEntry) parent).mayEditChildren() != null) {
                throw new WGAuthorisationException("User is not allowed to edit child entries under this parent entry", (WGStructEntry) parent);
            }
        }



        if (!hasFeature(FEATURE_ACCEPTS_STRUCTKEYS)) {
            if (key != null) {
                throw new WGCreationException("You provided a struct key, but this implementation does not accept struct keys from the creator");
            }
        }

        if (!hasFeature(FEATURE_GENERATES_STRUCTKEYS)) {
            if (key == null) {
                throw new WGCreationException("You did not provide a struct key, but this implementation cannot generate struct keys itself");
            }

            WGStructEntry otherEntry = getStructEntryByKey(key);
            if (otherEntry != null) {
                throw new WGDuplicateKeyException("There is already a struct entry under the given key '" + String.valueOf(key) + "'");
            }
        }
    }

    /**
     * Returns a WGContent object for the given content core by either
     * retrieving an existing or creating one
     *
     * @param doc
     *            The content core
     * @return WGContent
     * @throws WGAPIException
     */
    protected WGContent getOrCreateContentObject(WGDocumentCore doc) throws WGAPIException {

        if (doc == null) {
            return null;
        }

        WGDocumentKey key = WGDocument.buildDocumentKey(doc, this);
        WGContent content;
        try {
            content = (WGContent) getDocumentByDocumentKeyFromCache(key, false);
        }
        catch (WGDocumentDoesNotExistException e) {
            content = null;
        }

        if (content == null) {
            content = this.createContentObject(doc);
        }
        else if (!content.isTemporary()) {
            if (!content.isEdited()) {
                content.setCore(doc);
            }

            // Make the content object appear in the user's private content
            // cache
            if (getSessionContext().isCacheWritingEnabled()) {
                mapContentObject(content, false);
            }
        }
        return content;

    }

    protected WGDesignDocument getOrCreateDesignDocumentObject(WGDocumentCore doc) throws WGAPIException {

        WGDocumentKey key = WGDocument.buildDocumentKey(doc, this);
        WGDesignDocument design;
        try {
            design = (WGDesignDocument) getDocumentByDocumentKeyFromCache(key, false);
        }
        catch (WGDocumentDoesNotExistException e) {
            design = null;
        }

        if (design == null) {
            design = createDesignDocumentObject(doc);
        }
        else {
            if (!design.isEdited()) {
                design.setCore(doc);
            }
        }
        return design;

    }

    /**
     * Wraps a user profile core into a WGUserProfile object.
     *
     * @param doc
     *            core to wrap
     * @return WGUserProfile
     * @throws WGAPIException
     */
    private WGUserProfile createUserProfileObject(WGDocumentCore doc) throws WGAPIException {

        WGUserProfile profile = new WGUserProfile(this, doc);
        mapDocumentObject(profile);
        return profile;
    }

    /**
     * Returns date, where data in the database was last changed. This function
     * may not be supported by some implementations (Feature
     * WGDatabase.FEATURE_LASTCHANGED) and return new Date(Long.MIN_VALUE).
     *
     * @return Date
     * @throws WGAPIException
     */
    public Date getLastChanged() {
        return _revisionDate;
    }
   
    /**
     * Returns an indicator value about the revision state of the database, which may be a date, some kind of number or any other {@link Comparable}
     * Revisions that in comparison are "larger" than other revisions are regarded "later".
     * To get dates corresponding to the given revision indicators use {@link #getRevisionDate(Comparable)}.
     * To directly retrieve concrete dates use {@link #getLastChanged()} instead.
     */
    public Comparable getRevision() {
        return this._revision;
    }
   
    /**
     * Retrieve the datetime where a given database revision was done.
     * The indicators given as parameter must have been retrieved by {@link #getRevision()}.
     * If the revision is of wrong datatype or is no revision known in this database the method throws a WGWrongRevisionExcception.
     * @param lastModified The revision indicator
     * @return The date that the given revision was done
     * @throws WGAPIException
     * @throws WGWrongRevisionException if the given revision is no revision of the current database
     */
    public Date getRevisionDate(Comparable lastModified) throws WGAPIException, WGWrongRevisionException {
        return getCore().getRevisionDate(lastModified);
    }

    /**
     * Returns the map of creation options provided to this database when it was
     * initially opened
     *
     * @return Map
     */
    public Map getCreationOptions() {

        return this.creationOptions;

    }

    /**
     * Returns a map of WGLanguage objects, mapped by their language name (code)
     *
     * @return Map
     * @throws WGAPIException
     */
    public Map<String,WGLanguage> getLanguages() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        List languageList = this.getDesignObjects(WGDocument.TYPE_LANGUAGE);
        if (languageList == null) {
            return null;
        }

        Iterator languageIterator = languageList.iterator();

        HashMap languages = new HashMap<String,WGLanguage>();
        WGLanguage language = null;

        while (languageIterator.hasNext()) {
            language = (WGLanguage) languageIterator.next();
            languages.put(language.getName(), language);
        }

        return languages;

    }

    /**
     * Returns the session context, providing all data about the currently
     * opened database session. Null if no session is open.
     *
     * @return WGSessionContext
     */
    public WGSessionContext getSessionContext() {

        return (WGSessionContext) this.sessionContext.get();
    }

    /**
     * Clears all document caches.
     */
    private synchronized void clearDocumentMappings() {

        this.masterDocumentsByKey.clear();
        this.designDocumentLists.clear();
        this.structParents.clear();
        this.contentByKey.clear();
        this.contentByName.clear();
        this.queryCache.clear();
        WGDocument.uniqueKeys.clear();
        getCache().flushDatabaseCache(this);

    }

    /**
     * Completely closes this database object. This object and all child objects
     * must not be used after calling this.
     *
     * @throws WGAPIException
     */
    public void close() throws WGAPIException {

        WGFactory.getInstance().removeDatabase(this);
        ready = false;

        if (isSessionOpen()) {
            this.closeSession();
        }

        if (this.designProvider != null) {
            this.designProvider.removeDesignChangeListener(this);
            this.designProvider.dispose();
            this.designProvider = null;
        }

        if (isConnected()) {
            core.close();
        }
       
        closeAuthenticationModule();

        // Cannot release db listeners here, bc. the closing might be issued by
        // a fired event (e.g. connection error), and this would result in a
        // concurrent
        // modification.
        // databaseConnectListeners.clear();

    }

    private void closeAuthenticationModule() {
        if (_authenticationModule != null) {
            _authenticationModule.removeAuthenticationSourceListener(this);
            _authenticationModule.destroy();
        }
    }

    /**
     * Retrieves a dummy content object. Should support the native expression
     * language of the database, if there is one.
     *
     * @return WGContent
     * @throws WGAPIException
     */
    public WGContent getDummyContent(String language) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (language == null) {
            language = getDefaultLanguage();
        }

        if (hasFeature(FEATURE_MULTILANGUAGE)) {
            WGLanguage lang = getLanguage(language);
            if (lang == null) {
                throw new WGIllegalArgumentException("Language '" + language + "' is not defined");
            }
        }

        WGContent content = new WGContent(this, this.getCore().getDummyContent(language));
        content.setDummy(true);
        return content;
    }

    /**
     * Returns a dummy profile object that does not store information.
     *
     * @param name
     *            Profile name that the dummy profile should return
     * @throws WGAPIException
     */
    public WGUserProfile getDummyProfile(String name) throws WGAPIException {

        WGUserProfile profile = new WGUserProfile(this, new WGFakeUserProfile(this, name));
        profile.setDummy(true);
        return profile;

    }

    /**
     * Parses the string representation of a struct key (e.g. used in a URL) to
     * the real internal struct key format
     *
     * @param key
     * @return The struct key object. Type varies by database implementation.
     * @throws WGAPIException
     */
    public Object parseStructKey(String key) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return this.getCore().parseStructKey(key);
    }

    /**
     * Return a content document by content key
     *
     * @param key
     *            Key to retrieve content for
     * @return WGContent the found content, if none found null
     * @throws WGAPIException
     */
    public WGContent getContentByKey(WGContentKey key) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (key == null) {
            return null;
        }

        WGContent content = null;

        // A released version is needed. Find via struct entry
        if (key.getVersion() == 0) {
            WGStructEntry entry = getStructEntryByKey(key.getStructKey());
            if (entry == null) {
                return null;
            }

            return entry.getReleasedContent(key.getLanguage());
        }

        // A certain version is needed. Find via document key
        else {
            return (WGContent) getDocumentByKey(key.toDocumentKey());
        }
    }

    private WGContent retrieveContentByKey(WGContentKey key) throws WGAPIException {

        WGContent content = null;

        WGDocumentCore contentCore = this.getCore().getContentByKey(key);
        if (contentCore != null && !contentCore.isDeleted()) {
            content = this.getOrCreateContentObject(contentCore);
        }
        else {
            if (key.getVersion() != 0) {
                mapNonExistentDocIndicator(this.contentByKey, key.toString(), WGDocument.TYPE_CONTENT);
            }
            // DON'T add a nullplace holder to masterDocumentsByKey here. A
            // content may be invisible to the current user, yet visible to
            // another one.
        }

        return content;

    }

    protected void mapNonExistentDocIndicator(Map cacheMap, Object cacheKey, int docType) {

        if (getSessionContext().isCacheWritingEnabled() && isDoctypeCacheable(docType)) {
            cacheMap.put(cacheKey, new NullPlaceHolder());
        }

    }

    /**
     * Return a content document by content key string.
     *
     * @param keyStr
     *            The content key in its string representation
     * @return The content with that content key or null if it does not exist
     * @throws WGAPIException
     */
    public WGContent getContentByKey(String keyStr) throws WGAPIException {
        WGContentKey contentKey = WGContentKey.parse(keyStr, this);
        if (contentKey != null) {
            return getContentByKey(contentKey);
        }
        else {
            throw new WGIllegalArgumentException("Invalid content key string: " + keyStr);
        }
    }

    /**
     * Fetches a document from any WGDatabase document cache. If the cache entry
     * contains an object that is no document this is taken as indicator that
     * the document does not exist (It already was tried to retrieve it but that
     * failed). To let the calling code differ between empty cache and
     * nonexistent document the method throws a WGDocumentDoesNotExistException
     * in that case.
     *
     * @param cache
     *            The cache map to use
     * @param key
     *            The cache key
     * @return The cached document. null if the cache entry is empty (not yet
     *         cached)
     * @throws WGAPIException
     * @throws WGDocumentDoesNotExistException if the document is marked in cache as nonexistent
     * @throws WGAPIException
     */
    protected WGDocument fetchDocumentFromCache(Map cache, Object key) throws WGAPIException {

        /*
         * Is this really neccessary? We already bypass all caches on the
         * document objects themselves in uncached mode so there is no real need
         * to avoid using an already existing document object and create a new
         * one this only leads to duplicate document objects if
         * (!getSessionContext().isCachingEnabled()) { return null; }
         */

        Object cacheContent = cache.get(key);

        // Direct cache hit (normally only masterDocumentsByKey): Return doc if not read protected now
        if (cacheContent instanceof WGDocument) {
            WGDocument doc = (WGDocument) cacheContent;
            if (doc instanceof WGContent) {
                WGContent content = (WGContent) doc;
                WGStructEntry structEntry = content.getStructEntry();
                if (structEntry != null && !structEntry.mayReadContent()) {
                    throw new WGDocumentDoesNotExistException();
                }
            }
            return doc;
        }

        // Indirect cache hit: Lookup document by its document key in master
        // cache
        else if (cacheContent instanceof WGDocumentKey) {
            return fetchDocumentFromCache(masterDocumentsByKey, (WGDocumentKey) cacheContent);
        }

        // Cache indicating document does not exist: Throw exception
        else if (cacheContent instanceof NullPlaceHolder) {
            throw new WGDocumentDoesNotExistException();
        }

        // Cache indication document is unknown. We must retrieve it from
        // backend.
        else {
            return null;
        }

    }

    /**
     * Returns a document by the given document key.
     *
     * @param key
     *            The document key for the document in its string representation
     * @return The document of that key, null if there is none
     * @throws WGAPIException
     * @deprecated Use {@link #getDocumentByKey(String)} instead
     */
    public WGDocument getDocumentByDocumentKey(String key) throws WGAPIException {
        WGDocumentKey documentKey = new WGDocumentKey(key);
        return getDocumentByKey(documentKey);
    }

    /**
     * Returns a document by the given document key.
     *
     * @param documentKey
     *            The document key for the document.
     * @return The document of that key, null if there is none
     * @throws WGAPIException
     */
    public WGDocument getDocumentByKey(WGDocumentKey documentKey) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (!documentKey.isRealDocumentKey()) {
            throw new IllegalArgumentException("The document key '" + documentKey + "' does not belong to a real WGAPI document");
        }
       
        if (hasFeature(FEATURE_DISABLE_META_LOWERCASING)) {
            documentKey.useProperCaseNames();
        }

        try {
            // Check cache once unsynchronized, then synchronized
            WGDocument doc = getDocumentByDocumentKeyFromCache(documentKey, true);
            if (doc == null) {
                WGOperationKey op = obtainOperationKey(WGOperationKey.OP_DOCUMENT_BY_KEY, documentKey.toString());
                synchronized (op) {
                    try {
                        op.setUsed(true);
                        doc = getDocumentByDocumentKeyFromCache(documentKey, true);

                        if (doc == null) {
                            int docType = documentKey.getDocType();
                            switch (docType) {
                                case WGDocument.TYPE_CONTENT:
                                    WGContentKey contentKey = WGContentKey.parse(documentKey.getName(), this);
                                    doc = retrieveContentByKey(contentKey);
                                    break;

                                case WGDocument.TYPE_USERPROFILE:
                                    doc = retrieveUserProfile(documentKey.getName());
                                    break;

                                case WGDocument.TYPE_STRUCTENTRY:
                                    Object structKey = parseStructKey(documentKey.getName());
                                    doc = retrieveStructEntry(structKey);
                                    break;

                                default:
                                    doc = retrieveDesignDocument(docType, documentKey.getName(), documentKey.getMediakey());
                                    break;
                            }
                        }
                    }
                    finally {
                        op.setUsed(false);
                    }
                }
            }
            return doc;
        }
        catch (WGDocumentDoesNotExistException e) {
            return null;
        }

    }
   
    /**
     * Returns a document by the given document key.
     *
     * @param key
     *            The document key for the document in its string representation
     * @return The document of that key, null if there is none
     * @throws WGAPIException
     */
    public WGDocument getDocumentByKey(String key) throws WGAPIException {
        WGDocumentKey documentKey = new WGDocumentKey(key);
        return getDocumentByKey(documentKey);
    }

    /**
     * Returns the parent entry to the given struct entry
     *
     * @param entry
     *            The entry, whose parent is searched
     * @return WGStructEntry
     * @throws WGAPIException
     */
    public WGStructEntry getParentEntry(WGStructEntry entry) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (entry == null || entry.isDeleted() || entry.isDummy()) {
            return null;
        }

        WGDocumentCore parentCore = this.getCore().getParentEntry(entry);
        if (parentCore != null && !parentCore.isDeleted()) {
            return getOrCreateStructEntryObject(parentCore);
        }
        else {
            return null;
        }

    }

    /**
     * Retrieves the value of an extension data field stored in this database.
     * Extension data fields are custom data that are persistently stored to an entity, in this case the database itself, and which may have custom purpose.
     * This feature is only available in WGA Content Stores of Version 5 or higher.
     * @param name The name of the field.
     * @return The value of the requested field for this database core.
     * @throws WGAPIException
     */

    public Object getExtensionData(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
        }

        return getCore().getExtensionData(name);
    }
   
    /**
     * Returns the names of stored extension data fields on this database
     * This feature is only available in WGA Content Stores of Version 5 or higher.
     * @return List of field mnames
     * @throws WGAPIException
     */
    public List<String> getExtensionDataNames() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
        }

        return getCore().getExtensionDataNames();
    }
   
    /**
     * Writes a extension data field. The data is immediately stored.
     * Extension data fields are custom data that are persistently stored to an entity, in this case the database itself, and which may have custom purpose.
     * This feature is only available in WGA Content Stores of Version 5 or higher.
     * @param name Name of the field
     * @param value The value to store
     * @throws WGAPIException
     */
    public void writeExtensionData(String name, Object value) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
        }
       
        if (getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_MANAGER) {
            throw new WGAuthorisationException("Only managers can modify database metadata");
        }

        getCore().writeExtensionData(name, value);
    }
   
    /**
     * Removes a extension data field from the database. The removal is immediately commited.
     * This feature is only available in WGA Content Stores of Version 5 or higher.
     * @param name Name of the field to remove
     * @throws WGAPIException
     */
    public void removeExtensionData(String name) throws WGAPIException {
       
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException("Database metadata", WGDatabase.CSVERSION_WGA5);
        }
       
        if (getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_MANAGER) {
            throw new WGAuthorisationException("Only managers can modify database metadata");
        }


        getCore().removeExtensionData(name);
       
    }

    /**
     * Returns the default language of this database
     *
     * @return String
     * @throws WGAPIException
     */
    public String getDefaultLanguage() throws WGAPIException {
       
        if (_defaultLanguage == null) {
            _defaultLanguage = Locale.getDefault().getLanguage();
        }
       
        return _defaultLanguage;
    }

    /**
     * Trigger the determination of a default language.
     * This method tries to find a reasonable safe language to use as default language with limited effort.
     * It does not take into account which language is used the most but will take a language with root contents available.
     * @throws WGAPIException
     */
    public synchronized void determineDefaultLanguage() throws WGAPIException {
       
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        // Look for root content languages
        WGContent content = null;
        Map langs = getLanguages();
        Iterator langsIt = getLanguages().values().iterator();
        while (langsIt.hasNext()) {
            WGLanguage lang = (WGLanguage) langsIt.next();
            content = getFirstReleasedContent(lang.getName(), true);
            if (content != null) {
                break;
            }
        }
       
        // If we found a root content in some language we take it as default language
        if (content != null) {
            _defaultLanguage = content.getLanguage().getName();
            return;
        }
       
        // If langs defined: Use the one matching the platform default
        // or the "first best" when no definition matches
        if (langs.size() > 0) {
            WGLanguage lang = getLanguageForLocale(Locale.getDefault());
            if (lang == null) {
                lang = (WGLanguage) langs.values().iterator().next();
            }
            _defaultLanguage = lang.getName();
            return;
        }

        // No langs defined: Use platform default language
        else {
            _defaultLanguage = Locale.getDefault().getLanguage();
        }
       
        _defaultLanguageAutoDetermined  = true;
    }

    /**
     * Returns the name of the database server (if there is one).
     *
     * @return String
     * @throws WGAPIException
     */
    public String getServerName() throws WGAPIException {
        return getCore().getServerName();
    }

    /**
     * Returns, if the database is ready for work.
     *
     * @return Returns a boolean
     */
    public boolean isReady() {
        return ready;
    }

    /**
     * Returns the core implementation object of this database.
     *
     * @return WGDatabaseCore
     */
    public WGDatabaseCore getCore() {
        return core;
    }
   
    /**
     * Returns the personalisation core implementation of this database, if it is a personalisation database.
     * @throws WGNotSupportedException
     * @throws WGNotSupportedException If the database is no personalisation database
     */
    public WGPersonalisationDatabaseCore getPersonalisationCore() throws WGNotSupportedException {
       
        WGDatabaseCore theCore = getCore();
        if (theCore instanceof WGPersonalisationDatabaseCore) {
            return (WGPersonalisationDatabaseCore) theCore;
        }
        else {
            throw new WGNotSupportedException("This database is no personalisation database");
        }
       
       
    }

    /**
     * Indicates if a database session is open
     */
    public boolean isSessionOpen() {
        return (this.getSessionContext() != null);
    }

    /**
     * Returns the database path, given as parameter of the method
     * WGDatabase.open()
     *
     * @return String
     */
    public String getPath() {
        return path;
    }

    /**
     * Returns the first released content document of this database in the
     * specified language. It is up to the db implementation to decide, which
     * document is regarded "the first" of the database.
     *
     * @param language
     *            The language of the content
     * @param onlyRoots
     *            Specify true, if you want to get only root documents
     * @return WGContent The content, null if there is no content of that
     *         language.
     * @throws WGAPIException
     */
    public WGContent getFirstReleasedContent(final String language, boolean onlyRoots) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        WGLanguageChooser chooser = new WGLanguageChooser() {

            public WGContent selectContentForName(WGDatabase db, String name, boolean isBI) throws WGAPIException {
                return db.getContentByName(name, language);
            }

            public WGContent selectContentForPage(WGStructEntry page, boolean isBI) throws WGAPIException {
                return page.getReleasedContent(language);
            }

            public WGLanguage selectDatabaseLanguage(WGDatabase db) throws WGAPIException {
                return db.getLanguage(language);
            }

            public WGContentKey chooseQueryContent(WGDatabase db, Map<String,WGContentKey> contents) throws WGAPIException {
                return contents.get(language);
            }

            public List<WGLanguage> getQueryLanguages(WGDatabase db) throws WGAPIException {
                return Collections.singletonList(db.getLanguage(language));
            }
           
        };
       
        Iterator areas = this.getAreas().iterator();
        while (areas.hasNext()) {
            WGArea area = (WGArea) areas.next();
            if (area.isSystemArea()) {
                continue;
            }
           
            Iterator rootEntries = area.getRootEntries().iterator();
            while (rootEntries.hasNext()) {
                WGContent content = this.getFirstReleasedContent((WGStructEntry) rootEntries.next(), chooser, onlyRoots);
                if (content != null) {
                    return content;
                }
            }
        }

        return null;

    }
   
    /**
     * Returns the first released content document of this database.
     * It is up to the db implementation to decide, which document is regarded "the first" of the database.
     *
     * @param chooser
     *            A language chooser object determining what language versions may be used
     * @param onlyRoots
     *            Specify true, if you want to get only root documents
     * @return WGContent The content, null if there is no content of that
     *         language.
     * @throws WGAPIException
     */
    public WGContent getFirstReleasedContent(WGLanguageChooser chooser, boolean onlyRoots) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        Iterator areas = this.getAreas().iterator();
        while (areas.hasNext()) {
            WGArea area = (WGArea) areas.next();
            if (area.isSystemArea()) {
                continue;
            }
           
            Iterator rootEntries = area.getRootEntries().iterator();
            while (rootEntries.hasNext()) {
                WGContent content = getFirstReleasedContent((WGStructEntry) rootEntries.next(), chooser, onlyRoots);
                if (content != null) {
                    return content;
                }
            }
        }

        return null;

    }

    /**
     * Returns the first released content of the given language to be found in
     * this database.
     *
     * @param language
     * @throws WGAPIException
     */
    public WGContent getFirstReleasedContent(String language) throws WGAPIException {
        return getFirstReleasedContent(language, false);
    }

    /**
     * Returns the first released content in a given language, that is connected
     * to the given struct entry or to his descendant entries.
     *
     * @param entry
     *            The reference entry. Content will be attached to this one or
     *            to a descendant entry of it.
     * @param language
     *            Language of the needed content
     * @param onlyRoots
     *            Specify true, if you only want root documents returned
     * @return WGContent The content, null if there was no content of this
     *         language found.
     * @throws WGAPIException
     */
    private WGContent getFirstReleasedContent(WGStructEntry entry, WGLanguageChooser chooser, boolean onlyRoots) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (entry == null || entry.isDeleted() || entry.isDummy()) {
            return null;
        }

        WGContent content = chooser.selectContentForPage(entry, false);
        if (content != null) {
            return content;
        }
        else if (!onlyRoots) {
            Iterator childEntries = entry.getChildEntries().iterator();
            while (childEntries.hasNext()) {
                content = this.getFirstReleasedContent((WGStructEntry) childEntries.next(), chooser, onlyRoots);
                if (content != null) {
                    return content;
                }
            }
            return null;
        }
        else {
            return null;
        }
    }

    /**
     * Retrieves a user profile for the given profile name.
     *
     * @param name
     * @throws WGAPIException
     */
    public WGUserProfile getUserProfile(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (name == null) {
            return null;
        }

        return (WGUserProfile) getDocumentByKey(new WGDocumentKey(WGDocument.TYPE_USERPROFILE, name, null));

    }

    private WGUserProfile retrieveUserProfile(String name) throws WGAPIException {

        WGUserProfile profile = null;
        WGDocumentCore profileCore = getPersonalisationCore().getUserProfile(name);
        if (profileCore != null && !profileCore.isDeleted()) {
            profile = this.createUserProfileObject(profileCore);
        }
        else {
            mapNonExistentDocIndicator(masterDocumentsByKey, new WGDocumentKey(WGDocument.TYPE_USERPROFILE, name, null), WGDocument.TYPE_USERPROFILE);
        }

        return profile;
    }

    /**
     * Creates a new user profile.
     *
     * @param userNameWish
     *            Name of the new user profile
     * @param type
     *            Type of the user profile. Can be any integer chosen by the
     *            personalisation implementation to differ profile types.
     * @return The new user profile.
     * @throws WGAPIException
     *             instance of WGCreationException if a profile with this name
     *             already exists or the profile creation fails otherwise
     *             WGAPIExceptions on BackendErrors
     */
    public WGUserProfile createUserProfile(String userNameWish, int type) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        // check db lock
        if (getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create userprofile. Database is locked.");
        }

        if (getSessionContext().getAccessLevel() < ACCESSLEVEL_AUTHOR) {
            if (!(readerProfileCreation && getSessionContext().getAccessLevel() >= ACCESSLEVEL_READER)) {
                throw new WGAuthorisationException("User is not allowed to create user profiles in this database");
            }
        }

        if (userNameWish != null) {
            WGUserProfile profile = this.getUserProfile(userNameWish);
            if (profile != null) {
                throw new WGDuplicateKeyException("There is already a user profile with id '" + userNameWish + "'");
            }
        }

        WGDocumentCore profileCore = getPersonalisationCore().createUserProfile(userNameWish, type);
        if (profileCore == null) {
            throw new WGCreationException("Unable to create user profile. Maybe databases of type " + getCore().getClass().getName() + " do not support creating user profiles.");
        }
        WGUserProfile profile = this.createUserProfileObject(profileCore);
        return profile;

    }

    /**
     * Returns, if the database contains content
     *
     * @return boolean
     */
    public boolean isContentRole() {
        return contentRole;
    }

    /**
     * Returns if the database contains design documents
     *
     * @return boolean
     */
    public boolean isDesignRole() {
        return designRole;
    }

    /**
     * Returns if if the database contains file containers.
     *
     * @return boolean
     */
    public boolean isRepositoryRole() {
        return repositoryRole;
    }

    /**
     * Indicates if the database contains user profiles
     */

    public boolean isUserProfilesRole() {
        return userProfilesRole;
    }

    /**
     * Determines if this database is completely empty of (content store)
     * documents. This does not work on personalisation databases.
     *
     * @throws WGAPIException
     */
    public boolean isEmpty() throws WGAPIException {

        boolean contentEmpty = true;
        boolean designEmpty = true;

        if (isContentRole()) {
            contentEmpty = isContentEmpty();
        }

        if (isDesignRole()) {
            designEmpty = (isDesignEmpty());
        }

        return (contentEmpty && designEmpty);

    }

    /**
     * Returns if this database has no design documents (file containers, script
     * modules, WebTML modules) and can be regarded "design-empty".
     *
     * @throws WGAPIException
     */
    public boolean isDesignEmpty() throws WGAPIException {
        return getFileContainers().size() == 0 && getCSSJSModules().size() == 0 && getTMLModules().size() == 0;
    }

    /**
     * Returns if this database has neither content hierarchy (areas, struct
     * entries, contents) nor content schema documents (content types,
     * languages) and can be regarded "content-empty"
     *
     * @throws WGAPIException
     */
    public boolean isContentEmpty() throws WGAPIException {
        return (getAreas().size() == 0 && getContentTypes().size() == 0 && getLanguages().size() == 0);
    }

    /**
     * Eventually does final cleanup after all sessions of this database
     * implementation have been closed.
     */
    protected void cleanup() {
        WGFactory.getLogger().debug("WGDatabase cleanup " + path);
        this.getCore().cleanup();
    }

    /**
     * Gets the name of the master login, which is the login user name used with
     * the initial call to WGDatabase.open().
     *
     * @return String
     */
    public String getMasterLoginName() {
        return masterLoginName;
    }

    /**
     * Gets the workflow engine object for this database.
     *
     * @return WGWorkflowEngine
     * @throws WGWorkflowException
     * @throws WGBackendException
     */
    public synchronized WGWorkflowEngine getWorkflowEngine() throws WGAPIException {

       
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return workflowEngine;

    }

    /**
     * Returns an object containing internal statistics about the database
     *
     * @return WGDatabaseStatistics
     */
    public WGDatabaseStatistics getStatistics() {

        return new WGDatabaseStatistics();
    }

    /**
     * Tests, if the current user can be associated with any entry in the list.
     * This is up to the database implementation to decide since name/group/role
     * mechanims may vary widely in different databases.
     *
     * @param list
     * @throws WGAPIException
     */
    public boolean isMemberOfUserList(List list) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return this.getCore().isMemberOfUserList(list);
    }

    /**
     * Creates a simple content document. This method can only be used, when the
     * underlying database implementation doesn't support full content features
     * like struct entries, content types etc.
     *
     * @param title
     *            Title of the new content document.
     * @return WGContent
     * @throws WGAPIException
     */
    public WGContent createContent(String title) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (!hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            return this.createContent((WGStructEntry) null, null, title);
        }
        else {
            throw new WGNotSupportedException("This creation method is not available with this database implementation, because this implementation needs more information to create a content.");
        }
    }

    /**
     * Creates a simple content document. This variant can be used in
     * implementations that don't have struct entries or content types and
     * generate their own keys. (e.g. some descendants of SimpleContentSource
     * like JDBCSource).
     *
     * @return The newly created content document.
     * @throws WGAPIException
     */
    public WGContent createContent() throws WGAPIException {
        return createContent("");
    }

    /**
     * Returns a list with all content types stored in this database
     *
     * @return List List of WGContentType objects
     * @throws WGAPIException
     */
    public List<WGContentType> getContentTypes() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return getDesignObjects(WGDocument.TYPE_CONTENTTYPE);
    }

    /**
     * Returns a content type document of the given name
     *
     * @param name
     *            The name of the content type
     * @return WGContentType The content type if one with that name is present,
     *         null otherwise
     * @throws WGAPIException
     */
    public WGContentType getContentType(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return (WGContentType) getDesignObject(WGDocument.TYPE_CONTENTTYPE, name);
    }

    /**
     * Returns the list of TML modules in this database.
     *
     * @throws WGAPIException
     */
    public List<WGTMLModule> getTMLModules() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return getDesignObjects(WGDocument.TYPE_TML);
    }

    /**
     * Returns the TML module for the given name and media key
     *
     * @param name
     * @param mediaKey
     * @throws WGAPIException
     */
    public WGTMLModule getTMLModule(String name, String mediaKey) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return (WGTMLModule) getDesignObject(WGDocument.TYPE_TML, name, mediaKey);
    }

    /**
     * Returns the language of that name (code) if it exists. If the language
     * does not exist a dummy language definition is returned to prevent
     * NullPointers bc. of nonexistent language definitions. You can test for a
     * dummy language definition by calling language.isDummy().
     *
     * @param name
     *            The language name
     * @return WGLanguage
     * @throws WGAPIException
     */
    public WGLanguage getLanguage(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (name == null) {
            return null;
        }

        WGLanguage lang = (WGLanguage) getDesignObject(WGDocument.TYPE_LANGUAGE, name);
        if (lang == null) {
            lang = (WGLanguage) createDesignDocumentObject(new WGFakeLanguage(this, name, "(Not defined)"));
            lang.setDummy(true);
        }
        return lang;
    }

    /**
     * Retrieves the best matching language definition for the given locale.
     * Language definitions are searched in descending details order:
     * language_COUNTRY_VARIANT language_COUNTRY language
     *
     * @param locale
     *            The locale to match
     * @return The language that matches the locale best, null if none could be
     *         found
     * @throws WGAPIException
     */
    public WGLanguage getLanguageForLocale(Locale locale) throws WGAPIException {

        if (locale == null) {
            return getLanguage(getDefaultLanguage());
        }

        String langKey;
        WGLanguage lang;
        if (!WGUtils.isEmpty(locale.getCountry()) && !WGUtils.isEmpty(locale.getVariant())) {
            langKey = locale.getLanguage() + "_" + locale.getCountry() + "_" + locale.getVariant();
            lang = getLanguage(langKey);
            if (lang != null && !lang.isDummy()) {
                return lang;
            }
        }

        if (!WGUtils.isEmpty(locale.getCountry())) {
            langKey = locale.getLanguage() + "_" + locale.getCountry();
            lang = getLanguage(langKey);
            if (lang != null && !lang.isDummy()) {
                return lang;
            }
        }

        langKey = locale.getLanguage();
        lang = getLanguage(langKey);
        if (lang != null && !lang.isDummy()) {
            return lang;
        }

        return null;

    }

    /**
     * Creates a new area definition.
     *
     * @param name
     *            The Name of the new area
     * @return The newly created area object.
     * @throws WGAPIException
     */
    public WGArea createArea(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_AREA, name, null);

        name = name.toLowerCase();

        WGArea area = this.getArea(name);
        if (area != null) {
            throw new WGDuplicateKeyException("There is already an area of name '" + name + "'");
        }

        WGDocumentCore areaCore = this.getCore().createDesignDocument(WGDocument.TYPE_AREA, name, null);
        if (areaCore == null) {
            throw new WGCreationException("Unable to create area. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of areas");
        }

        area = (WGArea) createDesignDocumentObject(areaCore);
        return area;
    }

    /**
     * Creates a new content type.
     *
     * @param name
     *            Name of the new content type.
     * @return The newly created content type object.
     * @throws WGAPIException
     */
    public WGContentType createContentType(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_CONTENTTYPE, name, null);

        WGContentType contentType = this.getContentType(name);
        if (contentType != null) {
            throw new WGDuplicateKeyException("There is already a content type of name '" + name + "'");
        }

        name = name.toLowerCase();

        WGDocumentCore ctCore = createDesignDocumentCore(WGDocument.TYPE_CONTENTTYPE, name, null);
        if (ctCore == null) {
            throw new WGCreationException("Unable to create content type. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of content types");
        }

        contentType = (WGContentType) createDesignDocumentObject(ctCore);
        contentType.setPositioning(WGContentType.POSITIONING_EVERYWHERE);
        // contentType.save();
        return contentType;

    }

    private WGDocumentCore createDesignDocumentCore(int type, String name, String mediaKey) throws WGAPIException {

        // check db lock
        if (getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create designobject with name '" + name + "'. Database is locked.");
        }

        if (designProvider != null && designProvider.providesType(type) && !isMetadataModule(type, name)) {
            return designProvider.createDesignDocument(type, name, mediaKey);
        }
        else {
            return getCore().createDesignDocument(type, name, mediaKey);
        }

    }

    /**
     * Creates a new TML module.
     *
     * @param name
     *            name of the new TML module.
     * @param mediaKey
     *            Media key of the new TML module.
     * @return The newly created TML module.
     * @throws WGAPIException
     */
    public WGTMLModule createTMLModule(String name, String mediaKey) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_TML, name, mediaKey);

        name = name.toLowerCase();
        mediaKey = mediaKey.toLowerCase();

        WGTMLModule tmlModule = this.getTMLModule(name, mediaKey);
        if (tmlModule != null) {
            throw new WGDuplicateKeyException("There is already a tml module of name '" + name + "' and media key '" + mediaKey + "'");
        }

        WGDocumentCore tmlCore = createDesignDocumentCore(WGDocument.TYPE_TML, name, mediaKey);
        if (tmlCore == null) {
            throw new WGCreationException("Unable to create TML module. Maybe databases of type " + getCore().getClass().getName() + " do not support creating TML modules.");
        }

        tmlModule = (WGTMLModule) createDesignDocumentObject(tmlCore);
        tmlModule.setDirectAccessAllowed(true);
        // tmlModule.save();
        return tmlModule;
    }

    /**
     * Moves a struct entry to a new position.
     *
     * @param entry
     *            The entry to move
     * @param newParent
     *            The new parent of the entry. If it is an WGArea object the
     *            struct entry will become a root document in this area. If it
     *            is an WGStructEntry it will become a child entry of this
     *            entry.
     * @return true, if the operation succeeded, false otherwise
     * @throws WGAPIException
     */
    public boolean moveStructEntry(WGStructEntry entry, WGDocument newParent) throws WGAPIException {

        if (isSessionOpen() == false) {
            throw new WGClosedSessionException();
        }

        performStructMoveCheck(entry, newParent);

        WGDocument oldParent = (entry.isRoot() ? (WGDocument) entry.getArea() : entry.getParentEntry());
        boolean result = getCore().moveStructEntry(entry, newParent);
        if (result == false) {
            return false;
        }

        oldParent.dropCache();
        newParent.dropCache();
        entry.dropCache();

        // Must ensure that these object versions are the ones in cache
        mapDocumentObject(oldParent);
        mapDocumentObject(newParent);
        mapDocumentObject(entry);
       
        // Fire event "content has been moved" for each and every influenced content
        processMovedDocuments(entry);       
       
        return true;

    }

    private void processMovedDocuments(WGStructEntry entry) throws WGAPIException {
        entry.visit(new WGPageVisitor() {
            public void visit(WGArea area) {};
            public void visit(WGStructEntry entry) {
                try {
                    entry.dropCache();
                }
                catch (WGAPIException e) {
                    WGFactory.getLogger().error("Exception firing event 'contentHasBeenMoved'", e);
                }
            };
            public void visit(WGContent content) {
                try {
                    content.dropCache();
                   
                    boolean shouldBeVisible = !(content.hasCompleteRelationships() && content.getStructEntry().getArea().isSystemArea());
                    if (content.isVisible() != shouldBeVisible) {
                        content.setVisible(shouldBeVisible);
                        content.save(content.getLastModified());
                    }
                   
                    WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_HASBEENMOVED, content.getDocumentKey(), content.getStructEntry().getContentType().getName(), content.getDatabase());
                    event.setContent(content);
                    fireContentEvent(event);
                }
                catch (WGAPIException e) {
                    WGFactory.getLogger().error("Exception firing event 'contentHasBeenMoved'", e);
                }
            }
        });
    }

    /**
     * Controls if the current user may move a struct entry to the given new parent. If so the method exists normally. If not an exception is thrown.
     * @param entry The entry to move
     * @param newParent The new parent of the entry. May be an {@link WGArea} or another {@link WGStructEntry}.
     * @throws WGAPIException If the user may not move the document. The exception informs about the reason.
     */
    public void performStructMoveCheck(WGStructEntry entry, WGDocument newParent) throws WGIllegalArgumentException, WGAPIException, WGAuthorisationException, ResourceIsLockedException {
        if (entry == null) {
            throw new WGIllegalArgumentException("Entry to move is null");
        }

        if (newParent == null) {
            throw new WGIllegalArgumentException("New parent is null");
        }

        if (newParent.getDocumentKey().equals(entry.getDocumentKey())) {
            throw new WGIllegalArgumentException("You are trying to make a structentry the child entry of itself!");
        }

        if (newParent instanceof WGStructEntry && ((WGStructEntry) newParent).isDescendantOf(entry)) {
            throw new WGIllegalArgumentException("You are trying to move a structentry into it's own child hierarchy!");
        }

        if (entry.getDatabase() != newParent.getDatabase()) {
            throw new WGIllegalArgumentException("The databases of the entry to move and the new parent document are different");
        }

        WGDocument restrictingDoc = entry.mayEditEntryAndContent();
        if (restrictingDoc != null) {
            throw new WGAuthorisationException("You are not allowed to edit this struct entry because document '" + restrictingDoc.getDocumentKey() + "' disallows it.");
        }

        if (!getSessionContext().getUserAccess().mayMoveStructEntries()) {
            throw new WGAuthorisationException("You are not allowed to move struct entries in this database");
        }

        if (newParent instanceof WGArea) {
            WGArea area = (WGArea) newParent;
            if (!area.mayEditAreaChildren()) {
                throw new WGAuthorisationException("You are not allowed to move structs to this area");
            }
        }
        else if (newParent instanceof WGStructEntry) {
            WGStructEntry parentEntry = (WGStructEntry) newParent;
            restrictingDoc = parentEntry.mayEditChildren();
            if (restrictingDoc != null) {
                throw new WGAuthorisationException("You are not allowed to move a struct entry below this parent entry because document '" + restrictingDoc.getDocumentKey() + "' disallows it.");
            }
        }

        if (!entry.getContentType().mayCreateChildEntry(newParent)) {
            throw new WGAuthorisationException("The content type of the struct cannot be used at the target position");
        }

        // check lock status of entry to move
        if (entry.getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Cannot move structentry. Given entry is locked.");
        }

        // check lock status of newParent
        if (newParent.getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Cannot move structentry. New parentEntry is locked.");
        }
       
        // Check edit rights for all documents in moved page hierarchy
        entry.performSubtreeModificationCheck();
       
    }

    /**
     * Creates a new Script module
     *
     * @param name
     *            Name of the new module
     * @param type
     *            Type of the module. A constant WGCSSJSModule.CODETYPE_...
     * @throws WGAPIException
     */
    public WGCSSJSModule createCSSJSModule(String name, String type) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_CSSJS, name, type);

        name = name.toLowerCase();

        WGCSSJSModule cssLib = this.getCSSJSModule(name, type);
        if (cssLib != null) {
            throw new WGDuplicateKeyException("There is already a css/js library of name '" + name + "' and type '"+ type + "'");
        }

        WGDocumentCore cssCore = createDesignDocumentCore(WGDocument.TYPE_CSSJS, name, type);
        if (cssCore == null) {
            throw new WGCreationException("Unable to create css/js library. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of css/js libraries");
        }

        cssLib = (WGCSSJSModule) createDesignDocumentObject(cssCore);
        cssLib.setMetaData(WGCSSJSModule.META_CODETYPE, type);
        // cssLib.save();
        return cssLib;

    }

    /**
     * Controls if the current user may create a design document with the given data. If so the method exists normally. If not an exception is thrown.
     * @param type The type of design document. Use constants WGDocument.TYPE_...
     * @param name The name of design document
     * @param mediaKey For WebTML modules specify media key, for script modules specify code type (Constants WGScriptModule.CODETYPE_...). For other types specify null.
     * @throws WGAPIException If the creation for the given data would fail
     */
    public void performDesignCreationCheck(int type, String name, String mediaKey) throws WGAPIException {
       
        if (getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create design document. Database is locked.");
        }
       
        if (!getSessionContext().isDesigner()) {
            throw new WGAuthorisationException("You are not authorized to create design documents in this database");
        }

        if (type == WGDocument.TYPE_CSSJS && isMetadataModule(WGDocument.TYPE_CSSJS, name) && !WGCSSJSModule.CODETYPE_XML.equals(mediaKey)) {
            throw new WGIllegalArgumentException("Metadata modules must be of codetype XML");
        }
       
        if (!isDoctypeModifiable(type)) {
            throw new WGAuthorisationException("Updating this type of design document via WGAPI is not permitted in this database: " + WGDocument.doctypeNumberToName(type));
        }
    }

    /**
     * Returns the script modules in this database.
     *
     * @throws WGAPIException
     */
    public List getCSSJSModules() throws WGAPIException {
        return getDesignObjects(WGDocument.TYPE_CSSJS);
    }

    /**
     * Returns the script module of the given name.
     *
     * @param name
     * @throws WGAPIException
     */
    public WGScriptModule getCSSJSModule(String name) throws WGAPIException {
       
        // Changed backend semantics with WGA5:
        // When no codetype given we iterate over the available codetypes and request the separately
       
        Iterator types = WGScriptModule.METAINFO_CODETYPE.getAllowedValues().iterator();
        while (types.hasNext()) {
            String type = (String) types.next();
            WGScriptModule mod = getCSSJSModule(name, type);
            if (mod != null) {
                return mod;
            }
        }
       
        return null;
    }
   
    /**
     * Returns the script module of the given name and type
     *
     * @param name
     * @throws WGAPIException
     */
    public WGScriptModule getCSSJSModule(String name, String type) throws WGAPIException {
        return (WGScriptModule) getDesignObject(WGDocument.TYPE_CSSJS, name, type);
    }

    /**
     * Returns the list of file containers in this database
     *
     * @throws WGAPIException
     */
    public List<WGFileContainer> getFileContainers() throws WGAPIException {
        return getDesignObjects(WGDocument.TYPE_FILECONTAINER);
    }

    /**
     * Return the file container of the given name
     *
     * @param name
     * @throws WGAPIException
     */
    public WGFileContainer getFileContainer(String name) throws WGAPIException {
        return (WGFileContainer) getDesignObject(WGDocument.TYPE_FILECONTAINER, name);
    }

    /**
     * Creates a new file container.
     *
     * @param name
     *            The name of the file container.
     * @return The newly created file container.
     * @throws WGAPIException
     */
    public WGFileContainer createFileContainer(String name) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_FILECONTAINER, name, null);

        name = name.toLowerCase();

        WGFileContainer file = this.getFileContainer(name);
        if (file != null) {
            throw new WGDuplicateKeyException("There is already a file container of name '" + name + "'");
        }

        WGDocumentCore fileCore = createDesignDocumentCore(WGDocument.TYPE_FILECONTAINER, name, null);
        if (fileCore == null) {
            throw new WGCreationException("Unable to create file container. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of file containers");
        }

        file = (WGFileContainer) createDesignDocumentObject(fileCore);
        // file.save();
        return file;

    }

    /**
     * Creates a new language definition only by name, which is also used as title
     *
     * @param name
     *            The name (i.e. language code) of the new language.
     *            language
     * @return The newly created language definition.
     * @throws WGAPIException
     */
    public WGLanguage createLanguage(String name) throws WGAPIException {
        return createLanguage(name, name);
    }
   
    /**
     * Creates a new language definition.
     *
     * @param name
     *            The name (i.e. language code) of the new language.
     * @return The newly created language definition.
     * @throws WGAPIException
     */
    public WGLanguage createLanguage(String name, String title) throws WGClosedSessionException, WGAPIException, WGDuplicateKeyException, WGCreationException {
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        performDesignCreationCheck(WGDocument.TYPE_LANGUAGE, name, null);

        name = name.toLowerCase();

        WGLanguage language = this.getLanguage(name);
        if (language != null && language.isDummy() == false) {
            throw new WGDuplicateKeyException("There is already a language of name '" + name + "'");
        }

        WGDocumentCore langCore = this.getCore().createDesignDocument(WGDocument.TYPE_LANGUAGE, name, null);
        if (langCore == null) {
            throw new WGCreationException("Unable to create language. Maybe databases of type " + getCore().getClass().getName() + " do not support creation of languages");
        }

        language = (WGLanguage) createDesignDocumentObject(langCore);
        language.setTitle(title);
        return language;
    }

    /**
     * Creates a draft copy of the given content document, keeping it's language
     *
     * @param content
     * @return The draft copy of the content.
     * @throws WGAPIException
     */
    public WGContent createDraftCopy(WGContent content) throws WGAPIException {

        return createDraftCopy(content, null);
    }

    /**
     * Creates a draft copy of the given content, using the language given as
     * parameter.
     *
     * @param content
     *            The content to copy
     * @param language
     *            The language for the draft copy. Specify null to keep the
     *            language of the original.
     * @return The draft copy. This document is already stored when it is
     *         returned.
     * @throws WGAPIException
     */
    public synchronized WGContent createDraftCopy(WGContent content, WGLanguage language) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (language == null) {
            language = content.getLanguage();
        }

        if (!content.isSaved()) {
            throw new WGIllegalStateException("Source content has not yet been saved");
        }

        // check if structentry is saved
        if (!content.getStructEntry().isSaved()) {
            throw new WGIllegalStateException("StructEntry of source content has no yet been saved.");
        }

        if (!language.isSaved()) {
            throw new WGIllegalStateException("Language for this content has not yet been saved");
        }

        // Test for validity and access rights
        if (content.getStatus().equals(WGContent.STATUS_RELEASE) == false && content.getStatus().equals(WGContent.STATUS_ARCHIVE) == false) {
            throw new WGIllegalStateException("This content is not in status RELEASE");
        }

        if (getSessionContext().getAccessLevel() < ACCESSLEVEL_AUTHOR) {
            throw new WGAuthorisationException("User is not allowed to create content in this database");
        }

        if (getSessionContext().getAccessLevel() < ACCESSLEVEL_EDITOR) {
            if (!content.isAuthorOrOwner()) {
                throw new WGAuthorisationException("You are not allowed to create a draft copy of this content bc. you are not the author of the published version.");
            }
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES) && content.getStructEntry().mayEditEntryAndContent() != null) {
            throw new WGAuthorisationException("User is not allowed to create content under this struct entry", content.getStructEntry());
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            WGContentType contentType = content.getStructEntry().getContentType();
            if (contentType != null && !contentType.mayCreateContent()) {
                throw new WGAuthorisationException("User is not allowed to use this content type");
            }
            else if (contentType == null && getSessionContext().isDesigner() == false) {
                throw new WGAuthorisationException("User is not allowed to use struct entries without content types");
            }
        }

        synchronized (this) {

            WGContent newContent = createNewDraftVersion(content, language);

            // Initialize
            if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
                newContent.setStatus(WGContent.STATUS_DRAFT);
                newContent.setMetaData(WGContent.META_AUTHOR, getSessionContext().getUser());
                newContentKeys.put(newContent.getContentKey(), null);
            }

            // Initialize by workflow engine
            WGWorkflow workflow = getWorkflowEngine().getWorkflow(newContent);
            workflow.initialize();
            if (!projectMode) {
                newContent.addWorkflowHistoryEntry("Draft copy created");
            }
            else {
                newContent.addWorkflowHistoryEntry("Changed to draft status in project mode");
            }
           
            WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_STATUSCHANGED, newContent.getDocumentKey(), content.getStructEntry().getContentType().getName(), this);
            event.setContent(newContent);
            fireContentEvent(event);

            // Save and return
            newContent.save();
            return newContent;
        }

    }

    private WGContent createNewDraftVersion(WGContent content, WGLanguage language) throws WGAPIException, WGCreationException {
        // Evaluate new version number
        int maxVersion = findNewVersionNumber(content.getStructEntry(), language);

        // We will update the last changed date only, when there are no
        // pending changes in background
        boolean updateLastChanged = !isDatabaseUpdatedInBackground();

        // Create core and pre-initialize to be a valid WGContent object
        WGDocumentCore docCore = getCore().createCopy(content.getCore());
        if (docCore == null) {
            throw new WGCreationException("Unable to create draft copy. Maybe databases of type " + getCore().getClass().getName() + " do not support copying content");
        }

        // If already saved by WGAPI-Implementation, the cache should be
        // selectively updated
        if (docCore.isSaved()) {
            documentSaved(content, content.getDocumentKeyObj(), true, true, updateLastChanged);
        }

        if (hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            docCore.setMetaData(WGContent.META_VERSION, new Integer(maxVersion));
            docCore.setMetaData(WGContent.META_LANGUAGE, language.getName());
        }

        // Create WGContent object
        WGContent newContent = createContentObject(docCore);
        return newContent;
    }

    /**
     * Creates a copy of a design document, automatically renaming it to avoid
     * name collisions.
     *
     * @param original
     *            The original design document.
     * @return The copy of the design document.
     * @throws WGAPIException
     */
    public WGDesignDocument createCopy(WGDesignDocument original) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        // check db lock
        if (getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("Unable to create copy of '" + original.getClass().getName() + "'. Database is locked.");
        }

        if (!original.mayEditDocument()) {
            throw new WGAuthorisationException("You are not allowed to create this kind of design document in this database");
        }

        WGDocumentCore copyCore = getCore().createCopy(original.getCore());
        if (copyCore == null) {
            throw new WGCreationException("Unable to create copy. Maybe databases of type " + getCore().getClass().getName() + " do not support copying this kind of design document");
        }

        WGDesignDocument copy = createDesignDocumentObject(copyCore);
        int copyNr = 1;
        while (getDesignObject(copy.getType(), "Copy #" + copyNr + " of " + copy.getName()) != null) {
            copyNr++;
        }
        copy.setName("Copy #" + copyNr + " of " + copy.getName());
        // copy.save();
        return copy;

    }

    /**
     * Indicates, if document caching is enabled globally for the database
     */
    public boolean isCachingEnabled() {
        return cachingEnabled;
    }

    /**
     * Controls if the document caching is enabled for this database. It is
     * enabled by default. Turning it off may speed up writing operations but
     * will mean significant overhead to recurring reading operations.
     */
    public void setCachingEnabled(boolean b) {
        cachingEnabled = b;
    }

    /**
     * Starts a database transaction. The database must support this, which is
     * indicated by the feature WGDatabase.FEATURE_TRANSACTIONS.
     *
     * @return If the starting of the transaction succeeds
     */
    public boolean beginTransaction() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        boolean result = getCore().beginTransaction();
        if (result == true) {
            getSessionContext().setTransactionActive(true);
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Commits a database transaction, executing all data changes since the
     * start of the transaction. A transaction must be started with
     * beginTransaction() before calling this. The database must support
     * transactions, which is indicated by the feature
     * WGDatabase.FEATURE_TRANSACTIONS.
     *
     * @return If the committing of the transaction succeeds
     */
    public boolean commitTransaction() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        boolean result = getCore().commitTransaction();
        if (result == true) {
            getSessionContext().setTransactionActive(false);
            MasterSessionTask performDBUpdates = new MasterSessionTask(this) {
               
                @Override
                protected void exec(WGDatabase db) throws Throwable {
                    db.checkDatabaseUpdates();                   
                }
            };
            try {
                performDBUpdates.runWithExceptions();
            }
            catch (Throwable e) {
                throw new WGBackendException("Unable to perform db update on transaction commit.", e);
            }
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Rolls a database transaction back, canceling all data changes done since
     * the start of the transaction. A transaction must be started with
     * beginTransaction() prior to doing this. The database must support
     * transactions, which is indicated by the feature
     * WGDatabase.FEATURE_TRANSACTIONS.
     *
     * @return If the rolling back of the transaction succeeds
     */
    public boolean rollbackTransaction() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        boolean result = getCore().rollbackTransaction();
        if (result == true) {
            getSessionContext().setTransactionActive(false);
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * Retrieves a WGACL object which manages the access control to this
     * database. Is only supported by databases with feature
     * WGDatabase.FEATURE_ACL_MANAGEABLE
     */
    public WGACL getACL() {

        WGACLCore core = getCore().getACL();
        if (core != null) {
            return new WGACL(this, core);
        }
        else {
            return null;
        }
    }

    /**
     * Returns a HashMap that will store userdefined data for the currently
     * logged in user. Each user has his own hash map. This information persists
     * sessions and can be fetched again in later sessions.
     */
    public UserHashMap getUserCache() {
        return userCache;
    }

    /**
     * Returns the group of UserHashMaps for this database. Should not be used
     * outside the WGAPI.
     */
    public UserHashMapGroup getUserHashMapGroup() {
        return userHashMapGroup;
    }

    /**
     * Returns the native backend object for this database if there is one.
     *
     * @throws WGBackendException
     */
    public Object getNativeObject() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return getCore().getNativeObject();

    }

    /**
     * Performs all the neccessary operations after a document has been saved: -
     * Update caches - fire events - set states Either param document or param
     * log must be filled to indicate the document
     *
     * @param document
     *            The document that has been saved, if it is available, else
     *            null
     * @param log
     *            An Update log that triggered this document saved operation
     * @param isNewDoc
     *            Determines if this is a newly created doc that has been saved
     *            for the first date
     * @param savedViaWGA
     *            Determines if the document has been saved via WGA (and not in
     *            background)
     * @throws WGAPIException
     */
    protected void documentSaved(WGDocument document, WGDocumentKey docKey, boolean isNewDoc, boolean savedByWGAPI, boolean updateLastChanged) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (getSessionContext().isTransactionActive()) {
            return;
        }

        // Operations only neccessary if the document is currently in cache =
        // document is present
        if (document != null) {

            document.dropCache();

            // Must be done to ensure that the most up-to-date object is mapped
            mapDocumentObject(document);

            // Clear related caches
            if (document instanceof WGContent) {
                WGContent content = (WGContent) document;

                newContentKeys.remove(content.getContentKey());

                if (content.getStatus() != WGContent.STATUS_DRAFT && content.getStatus() != WGContent.STATUS_REVIEW) {
                    queryCache.clear();
                    content.getStructEntry().dropCache();
                }
                if (isNewDoc) {
                    content.getStructEntry().dropCache();
                }
            }
            else if (document instanceof WGStructEntry) {
                WGStructEntry structEntry = (WGStructEntry) document;
                if (structEntry.isRoot()) {
                    structEntry.getArea().dropCache();
                }
                else {
                    structEntry.getParentEntry().dropCache();
                }

                // Drop cache of eventually previous parent
                WGDocument parent = getStructParent(structEntry.getStructKey(), true);
                if (parent != null) {
                    parent.dropCache();
                }

            }
        }
        // Must be done in case of temporary content that influenced the
        // query cache
        else if (docKey.getDocType() == WGDocument.TYPE_CONTENT) {
      queryCache.clear();
        }

        // Clear indirect caches
        int docType = WGDocument.doctypeNameToNumber(docKey.getTypename());
        if (isDesignDocumentType(docType)) {
            designDocumentLists.remove(new Integer(docType));
        }
        userCache.clear();

        // Fire content has been saved event
        if (docKey.getTypename().equals(WGDocument.TYPENAME_CONTENT)) {
            String contentType = null;
            if (document != null) {
                WGContent content = (WGContent) document;
                if (content.hasCompleteRelationships()) {
                    contentType = content.getStructEntry().getContentType().getName();
                }
            }
            WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_HASBEENSAVED, docKey.toString(), contentType, this);
            if (document != null) {
                event.setContent((WGContent) document);
            }
            fireContentEvent(event);
        }

        // Fire design change event
        if (docKey.getTypename().equals(WGDocument.TYPENAME_FILECONTAINER) || docKey.getTypename().equals(WGDocument.TYPENAME_TML) || docKey.getTypename().equals(WGDocument.TYPENAME_CSSJS)) {
            // Only fire when no design provider present. Otherwise the
            // listeners are registered at the provider and it is his
            // responsibility to throw the event
            if (getDesignProvider() == null) {
                List logs = new ArrayList();
                logs.add(new WGUpdateLog(WGUpdateLog.TYPE_UPDATE, new Date(), getSessionContext().getUser(), docKey.toString()));
                fireDatabaseDesignChangedEvent(new WGDesignChangeEvent(null, this, logs));
            }
        }

        if (!docKey.getTypename().equals(WGDocument.TYPENAME_USERPROFILE)) {
            updateCacheMaintenanceData();
        }

        if (savedByWGAPI) {
            this.getSessionContext().setDatabaseUpdated(true);

            if (updateLastChanged) {
                Comparable revision = getCore().getRevision();
                updateRevision(revision);
            }

            // Fire database event if the remove has been done by WGAPI
            // When this is done in background change processing, the event will
            // get fired after all changes by checkDatabaseUpdates
            fireDatabaseEvent(new WGDatabaseEvent(this, WGDatabaseEvent.TYPE_UPDATE, document));
        }
    }

    private synchronized void updateRevision(Comparable revision) throws WGAPIException {
        if (getSessionContext() != null && getSessionContext().isTransactionActive()) {
            return;
        }
        _revision = revision;
       
        if (_revision == null) {
            _revision = new Date(Long.MIN_VALUE);
        }
       
        if (_revision instanceof Date) {
            _revisionDate = (Date) _revision;
        }
        else {
            _revisionDate = getCore().getRevisionDate(_revision);
        }
       
    }

    private void updateCacheMaintenanceData() {
        lastCacheMaintenanceNanos = System.nanoTime();
        lastCacheMaintenance = new Date();
    }

    /**
     * Tests if the given document type is the type of a design document, i.e.
     * an area, contenttype, script module, file container, language definition
     * or tml module
     *
     * @param docType
     * @return true, if the doctype is a design document type, false if not
     */
    public static boolean isDesignDocumentType(int docType) {

        return (docType == WGDocument.TYPE_AREA || docType == WGDocument.TYPE_CONTENTTYPE || docType == WGDocument.TYPE_CSSJS || docType == WGDocument.TYPE_FILECONTAINER
                || docType == WGDocument.TYPE_LANGUAGE || docType == WGDocument.TYPE_TML);

    }

    /**
     * Returns the authentication module used to authenticate sessions, if this database supports and uses a auth module.
     * If this database uses a {@link RedirectionAuthModule} this method returns the real backend module. So this method should be used
     * to access the module if some special, non-standard, capabilities are to be used
     */
    public AuthenticationModule getAuthenticationModule() {
       
        AuthenticationModule backendModule = _authenticationModule;
        while (backendModule instanceof RedirectionAuthModule) {
            backendModule = ((RedirectionAuthModule) backendModule).getBackendModule();
        }
       
        return backendModule;
    }

    /**
     * Returns the password of the master login used to open this database.
     */
    public String getMasterLoginPassword() {
        return masterLoginPassword;
    }

    /**
     * Returns the maximum cores (i.e. backend document) that a session is
     * allowed to hold at once. If more are retrieved, the oldest cores will be
     * dropped to remain under this threshold.
     */
    public int getMaxCores() {
        return maxCores;
    }

    /**
     * Returns collected statistics of sessions that exceeded the maxdocs
     * threshold. A maximum of 100 statistics is collected. After that the
     * statistics of the top 100 statistics with the most docs retrieved are
     * kept.
     */
    public SortedBag getSessionStatistics() {
        return sessionStatistics;
    }

    /**
     * Sets the title of the database. This is not stored in the backend
     * database but only served via "getTitle()" until the database object is
     * closed.
     *
     * @param string
     *            The title to set
     */
    public void setTitle(String string) {
        title = string;
    }

    /**
     * Retrieves logs of documents that were modified since a cutoff revision. This
     * is only available in databases with feature WGDatabase.FEATURE_FIND_UPDATED_DOCS.
     * The returned list does not reflect all changes done to the documents but returns
     * a "condensed" list of operations reflecting the current state of documents
     * in the smallest possible way:
     * <ul>
     * <li>If a document has been modified multiple times in a row there will only be one update log, no matter how much updates have been done
     * <li>If a document was moved multiple times in a row and still exists there will be one move log, no matter how much updates have been done
     * </ul>
     *
     * @param cutoff
     *            The cutoff revision. Documents modified earlier will not be
     *            included in the result
     * @return List containing the log objects of type WGUpdateLog
     * @throws WGAPIException
     */
    public List getUpdatedDocumentsSince(Comparable cutoff) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        List<WGUpdateLog> logs = getCore().getUpdateLogs(cutoff);
        List<WGUpdateLog> condensedLogs = new ArrayList<WGUpdateLog>();
       
        // Filter logs to get the "condensed" list described in javadoc
        Map<String,Integer> lastOperation = new HashMap<String,Integer>();
       
        for (WGUpdateLog log : logs) {
           
            Integer op = lastOperation.get(log.getDocumentKey());
            if (op == null || op != log.getType()) {
                condensedLogs.add(log);
                lastOperation.put(log.getDocumentKey(), log.getType());
            }
        }
       
        return condensedLogs;

    }

    /**
     * Returns if the given struct entry contains a content document. This
     * method is capable of regarding content documents that the current user is
     * not allowed to see.
     *
     * @param entry
     *            The entry to test
     * @return true, if there exists a content (even those that the user may not
     *         see), false otherwise
     * @throws WGAPIException
     */
    public boolean hasContents(WGStructEntry entry) throws WGAPIException {

        if (isSessionOpen() == false) {
            throw new WGClosedSessionException();
        }

        if (entry == null || entry.isDeleted() || entry.isDummy()) {
            return false;
        }

        return (entry.getContentCount() > 0);

    }

    /**
     * Writes a dump of the document keys of all non-temporary WGDocument
     * objects currently in cache
     *
     * @param out
     *            The writer to write the dump to
     * @param lineDivider
     *            The divider to use between document keys
     * @throws IOException
     */
    public void dumpDocuments(Writer out, String lineDivider) throws IOException {

        if (out == null) {
            out = new PrintWriter(System.out);
        }

        synchronized (masterDocumentsByKey) {
            Iterator docIt = masterDocumentsByKey.values().iterator();
            while (docIt.hasNext()) {
                Object obj = docIt.next();
                if (obj instanceof WGDocument) {
                    WGDocument doc = (WGDocument) obj;
                    out.write(doc.getDocumentKey());
                    out.write(lineDivider);
                }
            }
        }

        out.flush();

    }

    /**
     * Returns a document by it's document key, only if it is contained in
     * cache.
     *
     * @param documentKeyStr
     *            The document key in its string representation
     * @return The document if it already is cached, null otherwise
     * @throws WGAPIException
     */
    public WGDocument getDocumentByDocumentKeyFromCache(String documentKeyStr) throws WGAPIException {

        WGDocumentKey documentKey = new WGDocumentKey(documentKeyStr);

        return getDocumentByDocumentKeyFromCache(documentKey);

    }

    /**
     * Returns a document by it's document key, only if it is contained in
     * cache.
     *
     * @param documentKey
     *            The document key
     * @return The document if it already is cached, null otherwise
     * @throws WGAPIException
     */
    public WGDocument getDocumentByDocumentKeyFromCache(WGDocumentKey documentKey) throws WGAPIException {
        try {
        return getDocumentByDocumentKeyFromCache(documentKey, true);
    }
        catch (WGDocumentDoesNotExistException e) {
            return null;
        }
    }

    private WGDocument getDocumentByDocumentKeyFromCache(WGDocumentKey documentKey, boolean useUserPrivateCaches) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        // Test session private cache
        WGSessionContext.DocumentContext docContext = getSessionContext().getDocumentContext(documentKey);
        if (docContext != null && docContext.getDocument() != null && !docContext.getDocument().isDeleted()) {
            return docContext.getDocument();
        }

        // Test public cache
        Map cacheMap;
        Object cacheKey;
        int docType = documentKey.getDocType();

        // In case of content we must take contentByKey, bc.
        // masterDocumentsByKey also contains Content that is not readable for
        // the current user
        if (docType == WGDocument.TYPE_CONTENT && !getSessionContext().isMasterSession() && useUserPrivateCaches) {
            cacheMap = contentByKey;
            cacheKey = documentKey.getName();
        }
        else {
            cacheMap = masterDocumentsByKey;
            cacheKey = documentKey;
        }

        WGDocument doc = fetchDocumentFromCache(cacheMap, cacheKey);
        if (doc != null && !doc.isDeleted()) {
            return doc;
        }
        else {
            return null;
        }
    }

    /**
     * Given a list of user/group names from an access control field (e.g.
     * META_READERS in Content) tests if anyone is allowed the operation.
     *
     * @param users
     *            The list of user/group names
     * @param emptyMeansYes
     *            Determines if an empty field (size = 0; first element null or
     *            empty string) should return true
     * @return true, if regarding this user/group list anyone is allowed
     */
    public static boolean anyoneAllowed(List users, boolean emptyMeansYes) {

        if (users == null) {
            return emptyMeansYes;
        }

        if (users.size() == 0 || (users.size() == 1 && (users.get(0) == null || users.get(0).toString().trim().equals("")))) {
            return emptyMeansYes;
        }

        if (users.contains("*")) {
            return true;
        }

        return false;

    }

    /**
     * Given a list of user/group names from an access control field (e.g.
     * META_READERS in Content) tests if anyone is allowed the operation. In
     * this version of the method, an empty field (size = 0 or first element
     * null or empty string) is always treated as true.
     *
     * @param users
     *            The list of user/group names
     * @return true, if regarding this user/group list anyone is allowed
     */
    public static boolean anyoneAllowed(List users) {
        return anyoneAllowed(users, true);
    }

    /**
     * Returns an Iterator iterating over all content documents in this database
     * does not include archived documents
     *
     * @throws WGAPIException
     */
    public Iterator getAllContent() throws WGAPIException {
        return getAllContent(false);
    }

    /**
     * Returns an Iterator iterating over all content documents in this database
     *
     * @param includeArchived
     *            Should archived documents been included? true/false
     * @throws WGAPIException
     */
    public Iterator getAllContent(boolean includeArchived) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        return new WGContentIterator(this, includeArchived);
    }

    private void setStructParent(WGStructEntry entry, WGDocument parent) {

        structParents.put(entry.getStructKey(), parent.getDocumentKeyObj());
        if (parent instanceof WGStructEntry) {
            entry.parentEntry = parent.getDocumentKeyObj();
        }
        else {
            entry.parentEntry = entry.getDocumentKeyObj();
        }

    }

    protected WGDocument getStructParent(Object structkey, boolean removeMapping) throws WGAPIException {

        WGDocumentKey ref;
        if (removeMapping) {
            ref = (WGDocumentKey) structParents.remove(structkey);
        }
        else {
            ref = (WGDocumentKey) structParents.get(structkey);
        }

        if (ref != null) {
            return (WGDocument) ref.getDocument(this);
        }
        else {
            return null;
        }

    }

    /**
     * Returns a database reference to be used throughout the application (in
     * WGA: the database key)
     */
    public String getDbReference() {
        return dbReference;
    }

    /**
     * Sets a reference for this database to use throughout the application.
     */
    public void setDbReference(String dbReference) {
        this.dbReference = dbReference;
    }

    /**
     * Returns the WGAPI cache object for the current database
     */
    public WGAPICache getCache() {
        return WGFactory.getInstance().getCache();
    }

    /**
     * Default implementation for building user details for an authentication
     * session. This one can be used by DB implementations that implement
     * FEATURE_ACL_MANAGEABLE. This method is not for direct usage outside of
     * the WGAPI.
     *
     * @param authSession
     *            The authentication session to create details for
     * @return User details object
     * @throws WGBackendException
     */
    public WGUserDetails defaultBuildUserDetails(AuthenticationSession authSession) throws WGBackendException {

        // First try to retrieve from user cache
        Map userCache = getUserCache().getMapForUser(authSession.getDistinguishedName());
        WGUserDetails userDetails = (WGUserDetails) userCache.get(USERCACHE_USERDETAILS);
        if (userDetails != null && !userDetails.isOutdated()) {
            return userDetails;
        }

        WGACL acl = getACL();
        Map users = acl.getUsers();
        Set entries = users.keySet();
        WGACLEntry accessLevelEntry = null;
        Set roles = new HashSet();
        List matchingEntries = new ArrayList();
        boolean inheritRoles = true;

        // Try to find matching username (always has highest priority for
        // accessLevelEntry)
        String matchingEntryName = (String) WGUtils.containsAny(entries, authSession.getNames());
        if (matchingEntryName != null) {
            accessLevelEntry = acl.getEntry(matchingEntryName);
            matchingEntries.add(accessLevelEntry.getName());
            WGACLEntryFlags parsedFlags = acl.parseFlags(accessLevelEntry);
            roles.addAll(parsedFlags.getRoles());
            if (parsedFlags.isNoRoleInheritance()) {
                inheritRoles = false;
            }
        }

        // Try to find matching groups (largest access level will be used as
        // accessLevelEntry)

        Set groups = new HashSet(entries);
        groups.retainAll(authSession.getGroups());
        if (groups.size() > 0) {
            // Some groups have been found - find out the one with the highest
            // level
            WGACLEntry maxEntry = null;
            WGACLEntry currentEntry = null;
            Iterator groupsIt = groups.iterator();
            boolean stopInheritingRoles = false;
            while (groupsIt.hasNext()) {
                currentEntry = (WGACLEntry) acl.getEntry((String) groupsIt.next());
                matchingEntries.add(currentEntry.getName());
                WGACLEntryFlags parsedFlags = acl.parseFlags(currentEntry);

                if (inheritRoles) {
                    roles.addAll(parsedFlags.getRoles());
                }

                if (parsedFlags.isNoRoleInheritance()) {
                    stopInheritingRoles = true;
                }

                if (maxEntry == null || currentEntry.getLevel() > maxEntry.getLevel()) {
                    maxEntry = currentEntry;
                }
            }

            if (accessLevelEntry == null) {
                accessLevelEntry = maxEntry;
            }

            if (stopInheritingRoles) {
                inheritRoles = false;
            }

        }

        // If User != anonymous he is member of the predefined group
        // "authenticated"
        if (!authSession.getDistinguishedName().equals(WGDatabase.ANONYMOUS_USER)) {
            WGACLEntry authenticatedEntry = acl.getEntry(AUTHENTICATED_GROUP);
            if (authenticatedEntry != null) {
                matchingEntries.add(authenticatedEntry.getName());
                WGACLEntryFlags parsedFlags = acl.parseFlags(authenticatedEntry);

                if (inheritRoles) {
                    roles.addAll(parsedFlags.getRoles());
                }

                if (accessLevelEntry == null) {
                    accessLevelEntry = authenticatedEntry;
                }

                if (parsedFlags.isNoRoleInheritance()) {
                    inheritRoles = false;
                }
            }
        }

        // Try to find default entry.
        WGACLEntry defaultEntry = acl.getEntry("*");
        if (defaultEntry != null) {
            matchingEntries.add(defaultEntry.getName());
            if (inheritRoles) {
                roles.addAll(acl.parseFlags(defaultEntry).getRoles());
            }

            if (accessLevelEntry == null) {
                accessLevelEntry = defaultEntry;
            }
        }

        // Feed the settings by the matching entry, if available
        int accessLevel = WGDatabase.ACCESSLEVEL_NOACCESS;
        boolean mayDeleteDocs = false;
        boolean mayMoveStructs = false;
        if (accessLevelEntry != null) {
            accessLevel = accessLevelEntry.getLevel();
            WGACLEntryFlags flags = acl.parseFlags(accessLevelEntry);
            mayDeleteDocs = flags.isMayDeleteDocs();
            mayMoveStructs = flags.isMayMoveStructs();
        }

        // Feed cache
        String authSource = "(none)";
        if (_authenticationModule != null) {
            authSource = _authenticationModule.getAuthenticationSource();
        }
        userDetails = new WGUserDetails(accessLevel, authSession.getDistinguishedName(), authSession.getMailAddress(), authSource, authSession.getNames(), authSession.getGroups(), roles,
                matchingEntries, mayDeleteDocs, mayMoveStructs);

        if (authSession instanceof LabeledNamesProvider) {
            userDetails.setLabeledNames(((LabeledNamesProvider) authSession).getLabeledNames());
        }

        userCache.put(USERCACHE_USERDETAILS, userDetails);
        return userDetails;

    }

    /**
     * Default implementation for determining user list membership. This may be
     * used by DB-Implementations that return {@link WGUserDetails} to specify
     * detailed user information. This method is not intended for direct usage
     * outside of the WGAPI.
     *
     * @param userListOrig
     *            A list of user/group/rolenames to determine membership for.
     * @return true, if the current user is member, false if not.
     * @throws WGAPIException
     */
    public boolean defaultIsMemberOfUserList(List userListOrig) throws WGAPIException {

        if (getSessionContext().isMasterSession()) {
            return true;
        }

        List userList = WGUtils.toLowerCase(userListOrig);
        WGUserDetails userDetails = getSessionContext().getUserDetails();
        if (userDetails == null) { // Only possible when no authentication configured
            return true;
        }

        if (userList.contains(userDetails.getPrimaryName().toLowerCase())) {
            return true;
        }

        if (WGUtils.containsAny(userList, WGUtils.toLowerCase(userDetails.getAliases())) != null) {
            return true;
        }

        // Test groups
        if (WGUtils.containsAny(userList, WGUtils.toLowerCase(userDetails.getGroups())) != null) {
            return true;
        }

        // Test roles
        if (WGUtils.containsAny(userList, WGUtils.toLowerCase(userDetails.getRoles())) != null) {
            return true;
        }

        // Test predefined groups
        if (!getSessionContext().isAnonymous() && userList.contains(WGDatabase.AUTHENTICATED_GROUP)) {
            return true;
        }

        return false;

    }

    /**
     * Returns if this database allows modification of design documents for the
     * current session This method returs false if any of the doctypes file
     * container, script or tml module are not modifiable. For getting more
     * detailed information the method {@link #isDoctypeModifiable(int)} should
     * be used.
     */
    public boolean isAllowDesignModification() {

        if (getSessionContext() != null && getSessionContext().isMasterSession()) {
            return true;
        }

        return isDoctypeModifiable(WGDocument.TYPE_FILECONTAINER) || isDoctypeModifiable(WGDocument.TYPE_CSSJS) || isDoctypeModifiable(WGDocument.TYPE_TML);

    }

    /**
     * Sets if this database should allow modification of design documents This
     * call equals calling {@link #setDoctypeModifiable(int, boolean)} for the
     * doctypes file container, script and tml module to false.
     */
    public void setAllowDesignModification(boolean allowDesignUpdates) {
        setDoctypeModifiable(WGDocument.TYPE_TML, false);
        setDoctypeModifiable(WGDocument.TYPE_CSSJS, false);
        setDoctypeModifiable(WGDocument.TYPE_FILECONTAINER, false);
    }

    /**
     * Returns the names of all database attributes as a Set
     */
    public Set getAttributeNames() {
        return Collections.unmodifiableSet(customAttributes.keySet());
    }

    /**
     * Returns an external design provider that was set to this database, if
     * available
     */
    public WGDesignProvider getDesignProvider() {
        return designProvider;
    }

    /**
     * Sets an external design provider from which this database should retrieve
     * design documents. Which design document types are provided is up to the
     * provider.
     *
     * @param designProvider
     *            The design provider to set.
     * @throws LockException
     */
    public void setDesignProvider(WGDesignProvider designProvider) throws WGAPIException {

        if (this.designProvider != null) {
            this.designProvider.removeDesignChangeListener(this);
        }

        this.designProvider = designProvider;

        if (this.designProvider.isNotifying()) {
            this.designProvider.addDesignChangeListener(this);
        }

        if (isConnected()) {
            refresh();
        }
    }

    /**
     * Converts the name of a file to attach to a format that is accepted by the
     * database backend
     *
     * @param name
     *            file name
     * @return converted file name
     */
    protected String convertFileNameForAttaching(String name) {
        return getCore().convertFileNameForAttaching(name);
    }

    /**
     * Determines if certificate authentication is enabled for this database
     */
    public boolean certAuthEnabled() {
       
        // Overriding option COPTION_DISABLE_CERTAUTH has priority
        String certAuth = (String) this.getCreationOptions().get(COPTION_DISABLE_CERTAUTH);
        if (certAuth != null && certAuth.equalsIgnoreCase("true")) {
            return false;
        }
       
        // If auth module is cert auth capable we return its status
        AuthenticationModule authModule = getAuthenticationModule();
        if (authModule instanceof CertAuthCapableAuthModule) {
            CertAuthCapableAuthModule certMod = (CertAuthCapableAuthModule) authModule;
            return certMod.isCertAuthEnabled();
        }
       
        // No cert auth
        return false;
    }

   



   



    /**
     * Sets an option to a map if it is not already set
     *
     * @param options
     *            the map to use
     * @param key
     *            the key to set to the map
     * @param value
     *            the value to write to the map
     */
    public static void putDefaultOption(Map options, String key, Object value) {

        if (!options.containsKey(key)) {
            options.put(key, value);
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.innovationgate.webgate.api.locking.Lockable#lock(de.innovationgate
     * .webgate.api.locking.LockOwner)
     */
    public void lock(LockOwner owner) throws WGAPIException {
        getLockManager().obtainLock(this, owner);
    }

    /**
     * locks the database for the current sessioncontext
     *
     * @throws WGAPIException
     */
    public void lock() throws WGAPIException {
        WGSessionContext sessionContext = getSessionContext();
        lock((LockOwner) sessionContext);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * de.innovationgate.webgate.api.locking.Lockable#unlock(de.innovationgate
     * .webgate.api.locking.LockOwner)
     */
    public void unlock(LockOwner owner) {
        getLockManager().releaseLock(this, owner);
    }

    /**
     * unlocks the database for the current sessioncontext
     */
    public void unlock() {
        WGSessionContext sessionContext = getSessionContext();
        unlock(sessionContext);
    }

    /*
     * (non-Javadoc)
     *
     * @seede.innovationgate.webgate.api.locking.Lockable#getLockStatus(de.
     * innovationgate.webgate.api.locking.LockOwner)
     */
    public int getLockStatus(LockOwner owner) throws WGAPIException {
        return getLockManager().getLockStatus(this, owner);
    }

    /**
     * gets the lockstatus for the current sessioncontext
     *
     * @return A value indication the lock status. See constants on
     *         de.innovationgate.webgate.api.locking.Lock.
     * @throws WGAPIException
     */
    public int getLockStatus() throws WGAPIException {
        return getLockStatus(getSessionContext());
    }

    /*
     * (non-Javadoc)
     *
     * @see de.innovationgate.webgate.api.locking.Lockable#getParentLockable()
     */
    public Lockable getParentLockable() {
        return null; // db has no parent
    }

    protected LockManager getLockManager() {
        return _lockManager;
    }

    public void designChanged(final WGDesignChangeEvent event) {

        // If this is not yet connected there is no need for cache maintenance
        if (!isConnected()) {
            return;
        }

        WGDesignProvider designProvider = event.getDesignProvider();

        // If the source is a virtual design provider, then
        // cache management will happen automatically by WGDocument.save()
        if (designProvider instanceof WGVirtualDesignProvider) {
            return;
        }

        MasterSessionTask task = new MasterSessionTask(this) {
            protected void exec(WGDatabase db) throws Throwable {
                processChanges(event.getUpdateLogs());
            }
        };

        if (!task.run()) {
            WGFactory.getLogger().error("Exception on design change processing", task.getThrowable());
        }
    }

    /**
     * Queries a user profile database for stored profiles, based on their data.
     * The query syntax is up to the profile database implementation.
     *
     * @param type The type of the query
     * @param query Query to use for searching user profiles
     * @param params Parameters given to the query
     * @return A list of names of user profiles whose profile data match the query
     * @throws WGQueryException
     */
    public List queryUserProfileNames(String type, String query, Map params) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (params == null) {
            params = new HashMap();
        }

        return getPersonalisationCore().queryUserProfileNames(type, query, params);

    }
   
    /**
     * Returns the names of all user profiles that are stored in the personalisation database
     * @throws WGNotSupportedException If the database is not personalisation database
     * @throws WGAPIException
     */
    public Iterator getAllUserProfileNames() throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        return getPersonalisationCore().getAllUserProfileNames();
       
    }
    /**
     * Queries a user profile database for stored profiles, based on their data.
     * The query syntax is up to the profile database implementation.
     * This variant uses the default query language for profile queries without parameters
     *
     * @param query Query to use for searching user profiles
     * @return A list of names of user profiles whose profile data match the query
     * @throws WGQueryException
     */
    public List queryUserProfileNames(String query) throws WGAPIException {
        return queryUserProfileNames(null, query, null);
    }

    /**
     * Returns if this database is already connected to its backend. This may be
     * false, when the database was opened with WGFactory.prepareDatabase() and
     * not session has been opened since.
     *
     */
    public boolean isConnected() {
        return connected;
    }

    /**
     * A metadata module is a special version of a script module that is used to
     * store metadata about the state of the database. It has some special
     * behaviours: - It is never stored or retrieved from a design provider but
     * always from the original db (even if the design provider target hosts
     * metadata modules) - It is not synchronized or migrated along with other
     * data - It is always of code type XML Metadata modules are stored
     * technically as normal script modules with a prefix qualifier, retrieved
     * from constant WGDatabase.METADATA_MODULE_QUALIFIER.
     *
     * @param name
     *            The name of the metadata module
     * @return A metadata module
     * @throws WGAPIException
     */
    public WGScriptModule getMetadataModule(String name) throws WGAPIException {
        return getScriptModule(WGCSSJSModule.METADATA_MODULE_QUALIFIER + name, WGScriptModule.CODETYPE_XML);
    }

    /**
     * Creates a new metadata module. For details about metadata modules see
     * method getMetadataModule.
     *
     * @param name
     *            Name of the module to create
     * @return The created module
     * @throws LockException
     * @throws WGCreationException
     * @throws WGAuthorisationException
     */
    public WGCSSJSModule createMetadataModule(String name) throws WGAPIException {
        return createCSSJSModule(WGCSSJSModule.METADATA_MODULE_QUALIFIER + name, WGCSSJSModule.CODETYPE_XML);
    }

    /**
     * Returns the user cache latency in minutes. This is the time after which
     * all user specific caches get discarded automatically to allow changes in
     * the authentication backend to take effect. This setting can be specified
     * via creation option COPTION_USERCACHELATENCY. If it is 0 the user caches
     * are kept eternal unless the database data changes.
     */
    public int getUserCacheLatency() {
        return userCacheLatency;
    }

    /**
     * Clears all user-specific caches. These caches normally contain either
     * cached authentication/authorisation information or links to content
     * documents that these users are allowed to see.
     */
    public void clearUserCaches() {
        userHashMapGroup.clearAllMaps();
    }

    /**
     * Returns the currently configured behaviour of this database regarding the
     * request of nonexistent items
     */
    public NoItemBehaviour getNoItemBehaviour() {
        return noItemBehaviour;
    }

    protected Collator getDefaultLanguageCollator() {
        if (_defaultLanguageCollator == null) {
            try {
                _defaultLanguageCollator = Collator.getInstance(WGLanguage.languageNameToLocale(getDefaultLanguage()));
            }
            catch (Exception e) {
                WGFactory.getLogger().error("Error determining default language collator. Using default platform collator", e);
                _defaultLanguageCollator = Collator.getInstance();
            }
        }
        return _defaultLanguageCollator;
    }

    /**
     * Returns if users with reader access to this profile database may create
     * profile documents. This setting is effective when WGA Content Stores are
     * used directly as personalisation databases, where the Content Store ACL
     * also is effective for profile creation (while normal personalisation
     * databases are always opened via master login).
     */
    public boolean isReaderProfileCreation() {
        return readerProfileCreation;
    }

    /**
     * Returns a list of all content keys in the current database. This method
     * may use optimized backend operations for this task, which do not need to
     * traverse through WGAPI documents.
     *
     * @param includeArchived
     *            Specify true, to also retrieve content keys for archived
     *            contents
     * @return List of content key objects {@link WGContentKey}
     * @throws WGAPIException
     */
    public List getAllContentKeys(boolean includeArchived) throws WGAPIException {

        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        if (!hasFeature(FEATURE_RETRIEVE_ALL_CONTENTKEYS)) {
            throw new WGNotSupportedException("This operation is not supported by this WGAPI implementation");
        }

        return getCore().getAllContentKeys(includeArchived);

    }

    /**
     * Returns the system time where the last cache maintenance happened
     */
    public Date getLastCacheMaintenance() {
        if (lastCacheMaintenance != null) {
            return lastCacheMaintenance;
        }
        else {
            return new Date(Long.MIN_VALUE);
        }
    }

    /**
     * Notify database of the beginning of update operations via this
     * connection. This is only neccessary in cluster environments when the
     * underlying database connections are kept "readOnly" until a real update
     * happens.
     */
    public void beginUpdate() throws WGAPIException {
        getCore().beginUpdate();
    }

    /**
     * Returns if backend deletion checks are enabled, see
     * {@link #COPTION_DELETIONCHECK}
     */
    public boolean isDeletionCheck() {
        return deletionCheck;
    }

    public Class getChildNodeType() {
        return DocumentCollectionHierarchy.class;
    }

    public List getChildNodes() throws WGAPIException {
        return _allDocumentsHierarchy.getChildNodes();
    }

    public String getNodeKey() {
        return "db/" + getDbReference();
    }

    public String getNodeTitle(String language) throws WGAPIException {
        return getTitle();
    }

    public PageHierarchyNode getParentNode() {
        return null;
    }

    /**
     * Returns if project mode is enabled, see {@link #COPTION_PROJECTMODE}
     */
    public boolean isProjectMode() {
        return projectMode;
    }

    /**
     * Registers an action that should be executed when the database is
     * connected. If it already is connected the action is executed at once and
     * the method returns true. If it is not yet connected but only prepared for
     * connecting the method returns false. In that case the action is executed
     * at the time the database will be connected. This action is always
     * executed under master session rights. Exceptions occurring in the action
     * will be logged but never thrown to the outside.
     *
     * @param action
     *            The action to execute.
     * @return true if the action was executed immediately, false if it is not.
     */
    public synchronized boolean onConnect(ConnectAction action) {

        if (isConnected()) {
            try {
                action.run(this);
            }
            catch (Exception e) {
                WGFactory.getLogger().error("Error executing connect action", e);
            }
            return true;
        }
        else {
            _connectActions.add(action);
            return false;
        }

    }

    /**
     * Returns if automatic approval is enabled, see
     * {@link #COPTION_AUTOAPPROVE}.
     */
    public boolean isAutoApprove() {
        return autoApprove;
    }

    /**
     * Sets if automatic approval is enabled
     */
    public void setAutoApprove(boolean autoApprove) {
        this.autoApprove = autoApprove;
    }

    /**
     * Sets if documents of a given doctype are modifiable for non-master
     * sessions Functionalities that feed the design of a database with external
     * data might want to disable modifiability of those types that are served.
     * This functionality is only effective for design and schema documents,
     * those that are retrievable via
     * {@link #getDesignObject(int, String, String)}.
     *
     * @param type
     *            The doctype to set. Use WGDocument.TYPE_... constants
     * @param modifiable
     *            Whether to set the doctype modifiable or unmodifiable
     */
    public void setDoctypeModifiable(int type, boolean modifiable) {

        if (modifiable) {
            this.unmodifiableDoctypes.remove(new Integer(type));
        }
        else {
            this.unmodifiableDoctypes.add(new Integer(type));
        }
    }

    /**
     * Tests if documents of a given doctypes are modifiable for the current
     * session
     *
     * @param type
     *            The type to test
     * @return true, if docs of that type are modifiable
     */
    public boolean isDoctypeModifiable(int type) {

        // Master session may modify anything
        if (getSessionContext() != null && getSessionContext().isMasterSession()) {
            return true;
        }

        // If the doctype is provided by a design provider, it is not modifiable
        // by users
        WGDesignProvider provider = getDesignProvider();
        if (provider != null) {
            boolean providesType = provider.providesType(type);
            if (providesType == true) {
                return false;
            }
        }

        // Check for manually set unmodifiability
        return !this.unmodifiableDoctypes.contains(new Integer(type));
    }

    /**
     * Returns a content with the given name in any language. Prefers the
     * default language of the database. If no content with that name is found
     * it tries every language defined in the database
     *
     * @param name
     *            The name to search
     * @return The content if any was found or null
     * @throws WGAPIException
     */
    public WGContent getAnyContentByName(String name) throws WGAPIException {

        // Try default language first
        WGContent content = getContentByName(name);
        if (content != null) {
            return content;
        }

        // Iterate over languages and return the first match
        Iterator langs = getLanguages().values().iterator();
        while (langs.hasNext()) {
            WGLanguage lang = (WGLanguage) langs.next();
            content = getContentByName(name, lang.getName());
            if (content != null) {
                return content;
            }
        }

        return null;

    }

    /**
     * Manually set a default language for this database
     *
     * @param defaultLanguage
     *            The default language
     */
    public void setDefaultLanguage(String defaultLanguage) {
        _defaultLanguage = defaultLanguage;
        _defaultLanguageCollator = Collator.getInstance(WGLanguage.languageNameToLocale(_defaultLanguage));
    }

    /**
     * Adds a design change listener to this database. If the database uses a
     * design provider it is registered with the provider. Otherwise it is
     * registered as normal WGDatabaseEventListener. Not all design change
     * events will carry information about the document being updated.
     *
     * @param changeListener
     */
    public void addDesignChangeListener(WGDesignChangeListener changeListener) {
        if (getDesignProvider() != null) {
            getDesignProvider().addDesignChangeListener(changeListener);
        }
        else {
            this.databaseDesignChangeListeners.add(changeListener);
        }
    }

    /**
     * Removes a design change listener to this database. If the database uses a
     * design provider it is removed from the provider. Otherwise it is removed
     * as normal WGDatabaseEventListener.
     *
     * @param changeListener
     */
    public void removeDesignChangeListener(WGDesignChangeListener changeListener) {
        if (getDesignProvider() != null) {
            getDesignProvider().removeDesignChangeListener(changeListener);
        }
        else {
            this.databaseDesignChangeListeners.remove(changeListener);
        }
    }

    private void fireDatabaseDesignChangedEvent(WGDesignChangeEvent e) {

        if (!isSessionOpen()) {
            return;
        }

        if (!getSessionContext().isEventsEnabled()) {
            return;
        }

        if (getDesignProvider() != null) {
            return;
        }

        synchronized (databaseDesignChangeListeners) {
            Iterator listeners = this.databaseDesignChangeListeners.iterator();
            while (listeners.hasNext()) {
                WGDesignChangeListener listener = (WGDesignChangeListener) listeners.next();
                listener.designChanged(e);
            }
        }
    }

    /**
     * Returns the script module of the given name and codetype
     *
     * @param name
     * @param codetype
     * @throws WGAPIException
     */
    public WGScriptModule getScriptModule(String name, String codetype) throws WGAPIException {
        return getCSSJSModule(name, codetype);
    }
   
    /**
     * Returns the script module of the given name and any codetype
     *
     * @param name
     * @throws WGAPIException
     */
    public WGCSSJSModule getScriptModule(String name) throws WGAPIException {
        return getCSSJSModule(name);
    }

    /**
     * Returns the script modules in this database.
     *
     * @throws WGAPIException
     */
    public List<WGScriptModule> getScriptModules() throws WGAPIException {
        return getCSSJSModules();
    }

    /**
     * Create a new script module. Same as {@link #createCSSJSModule(String, String)} but better named.
     * @param name Name of the script module
     * @param type Code type of the module. Use constants WGScriptModule.CODETYPE_...
     * @return The newly created script module
     * @throws WGAPIException
     */
    public WGScriptModule createScriptModule(String name, String type) throws WGAPIException {
        return (WGScriptModule) createCSSJSModule(name, type);
    }

    protected int getListCacheRebuildThreshold() {
        return listCacheRebuildThreshold;
    }

    /**
     * Returns the operation keys of backend operations that are currently running
     * @return List of {@link WGOperationKey} objects
     */
    public List getCurrentBackendOperations() {

        List currentOps = new ArrayList();
        Iterator ops = operations.values().iterator();
        while (ops.hasNext()) {
            WGOperationKey op = (WGOperationKey) ops.next();
            if (op.isUsed()) {
                currentOps.add(op);
            }
        }
        return currentOps;

    }

    /**
     * Returns the list of content event listeners for this database. This list
     * is the actual event listener list used by this WGDatabase. Code using
     * iterators of this list should synchronize against the list
     */
    public List getContentEventListeners() {
        return contentEventListeners;
    }

    /**
     * Injects an authentication module to the database that should be used to authenticate users.
     * @param authenticationModule The module
     * @throws WGIllegalArgumentException
     */
    public void setAuthenticationModule(AuthenticationModule authenticationModule) throws WGIllegalArgumentException {
       
        closeAuthenticationModule();
       
        // Unpack the auth module to see its real capabilities
        AuthenticationModule backendModule = authenticationModule;
        while (backendModule instanceof RedirectionAuthModule) {
            backendModule = ((RedirectionAuthModule) backendModule).getBackendModule();
        }
       
        if (certAuthEnabled() && (!(backendModule instanceof CertAuthCapableAuthModule))) {
            throw new WGIllegalArgumentException("The given authentication module is not capable for certificate authentication, which is used by database '" + getDbReference() + "':" + authenticationModule.getAuthenticationSource());
        }
       
        _authenticationModule = authenticationModule;
        _authenticationModule.addAuthenticationSourceListener(this);
    }

    public void authenticationDataChanged() {
        getUserCache().clear();
    }
   
    /**
     * Returns the version of content store that this database represents. Returns constants CSVERSION_...
     * @throws WGAPIException
     */
    public double getContentStoreVersion() throws WGAPIException {
        return getCore().getContenStoreVersion();
    }

    protected AllDocumentsHierarchy getAllDocumentsHierarchy() {
        return _allDocumentsHierarchy;
    }
   
    /**
     * Implements the visitor pattern on the page hierarchy. The visitor visits all areas, all of their struct entries and all of their (non-archived) contents.
     * @param visitor
     * @throws WGAPIException
     */
    public void visit(WGPageVisitor visitor) throws WGAPIException {
       
        Iterator<WGArea> areas = getAreas().values().iterator();
        while (areas.hasNext()) {
            WGArea area = areas.next();
            area.visit(visitor);
           
        }
       
    }

    /**
     * Returns the server used to connect to this database
     */
    public WGDatabaseServer getServer() {
        return server;
    }
   
   
    /**
     * Completely clears this document of all content related documents, which is all contents, struct entries, areas, content types and languages
     * WARNING (in case this has not yet become clear): This single method call will delete ALL documents of the given types in the database!
     * @throws WGAPIException
     */
    public void clearContentData() throws WGAPIException {
       
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_MANAGER) {
            throw new WGAuthorisationException("This operation only may be done by a manager");
        }
       
        Iterator areas = getAreas().values().iterator();
        while (areas.hasNext()) {
            WGArea area = (WGArea) areas.next();
            area.remove();
        }
       
        Iterator cts = getContentTypes().iterator();
        while (cts.hasNext()) {
            WGContentType ct = (WGContentType) cts.next();
            ct.remove();
        }

        Iterator langs = getLanguages().values().iterator();
        while (langs.hasNext()) {
            WGContentType ct = (WGContentType) langs.next();
            ct.remove();
        }
       
    }

    protected long getLastCacheMaintenanceNanos() {
        return lastCacheMaintenanceNanos;
    }
   
    /**
     * Returns if the database can be accessed by anonymous users
     * @throws WGBackendException
     */
    public boolean isAnonymousAccessible() throws WGBackendException {
        // check if file is anonymous accessible
        boolean isAnonymousAccessible = false;
        // first check anonymous db access
        WGACLEntry anonymousEntry = getACL().getEntry(WGDatabase.ANONYMOUS_USER);
        if (anonymousEntry != null && anonymousEntry.getLevel() >= WGDatabase.ACCESSLEVEL_READER) {
            isAnonymousAccessible = true;
        } else if (anonymousEntry == null) {
            WGACLEntry defaultEntry = getACL().getEntry("*");
            if (defaultEntry != null && defaultEntry.getLevel() >= WGDatabase.ACCESSLEVEL_READER) {
                isAnonymousAccessible = true;   
            }
        }
        return isAnonymousAccessible;
    }
   
    public void enforceSchema(WGSchemaDefinition schemaDef) throws WGAPIException {
       
        if (!isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        if (!getSessionContext().isMasterSession()) {
            throw new WGAuthorisationException("Enforcing schema is only possible under master session");
        }
       
        if (!hasFeature(FEATURE_FULLCONTENTFEATURES)) {
            throw new WGIllegalStateException("Enforcing schema is only possible on full WGA Content Stores");
        }
       
        this.schemaDefinition = schemaDef;
        boolean languageCreated = false;
       
        for (WGSchemaDocumentDefinition docDef : this.schemaDefinition.getDocumentDefinitionsCache().values()) {
            if (docDef.isAutoCreate()) {
                WGDocument newDoc = null;
                if (docDef instanceof WGContentTypeDefinition) {
                    WGContentType ct = getContentType(docDef.getName());
                    if (ct == null) {
                        WGFactory.getLogger().info("Creating content type '" + docDef.getName() + "' from schema definition");
                        newDoc = createContentType(docDef.getName());
                    }
                }
                else if (docDef instanceof WGAreaDefinition) {
                    WGArea area = getArea(docDef.getName());
                    if (area == null) {
                        WGFactory.getLogger().info("Creating area '" + docDef.getName() + "' from schema definition");
                        newDoc = createArea(docDef.getName());
                    }
                }
                else if (docDef instanceof WGLanguageDefinition) {
                    WGLanguage lang = getLanguage(docDef.getName());
                    if (lang == null || lang.isDummy()) {
                        WGFactory.getLogger().info("Creating language '" + docDef.getName() + "' from schema definition");
                        newDoc = createLanguage(docDef.getName());
                        languageCreated = true;
                    }
                }
               
                if (newDoc != null) {
                    newDoc.save();
                }
            }
        }
       
        if (this.schemaDefinition.isCreateDefaultLanguage()) {
            if (getLanguages().size() == 0) {
                WGFactory.getLogger().info("Creating default language from schema definition");
                WGLanguage lang = createLanguage(getDefaultLanguage());
                lang.save();
            }
        }
       
        // If a language was created and the default language of this db was automatically determined we now redetermine it
        if (languageCreated && _defaultLanguageAutoDetermined) {
            determineDefaultLanguage();
        }
       
       
    }

    public WGSchemaDefinition getSchemaDefinition() {
        return schemaDefinition;
    }
   
    /**
     * Creates a schema definition containing all content types and areas in this database with their current metadata.
     * @throws WGAPIException
     */
    public WGSchemaDefinition createSchemaDefinition() throws WGAPIException {
       
        WGSchemaDefinition schemaDef = new WGSchemaDefinition();
        for (WGContentType ct : getContentTypes()) {
            schemaDef.addDocumentDefinition(ct.createSchemaDefinition());
        }
        for (WGArea area : getAreas().values()) {
            schemaDef.addDocumentDefinition(area.createSchemaDefinition());
        }
        for (WGLanguage lang : getLanguages().values()) {
            schemaDef.addDocumentDefinition(lang.createSchemaDefinition());
        }
       
        return schemaDef;
       
    }
   
    /**
     * Lowercases a metadata field value for this database if metadata lowercasing is not disabled
     * @param meta The value to conditionally lowercase
     */
    public String toLowerCaseMeta(String meta) {
       
        if (!hasFeature(WGDatabase.FEATURE_DISABLE_META_LOWERCASING)) {
            return meta.toLowerCase();
        }
        else {
            return meta;
        }
       
    }

    /**
     * Returns if visibility constraints of the hierarchical reader fields on areas and pages are enabled
     */
    public boolean isPageReadersEnabled() {
        return pageReadersEnabled;
    }
}
TOP

Related Classes of de.innovationgate.webgate.api.WGDatabase$SessionStatistic

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
ew'); ew');