/*******************************************************************************
* 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;
}
}