Package de.innovationgate.webgate.api

Source Code of de.innovationgate.webgate.api.WGDocument

/*******************************************************************************
* 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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
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.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import de.innovationgate.utils.NullPlaceHolder;
import de.innovationgate.utils.TemporaryFile;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.utils.XStreamUtils;
import de.innovationgate.webgate.api.WGSessionContext.DocumentContext;
import de.innovationgate.webgate.api.fake.WGFakeDocument;
import de.innovationgate.webgate.api.locking.Lock;
import de.innovationgate.webgate.api.locking.LockException;
import de.innovationgate.webgate.api.locking.LockOwner;
import de.innovationgate.webgate.api.locking.Lockable;
import de.innovationgate.webgate.api.locking.ResourceIsLockedException;
import de.innovationgate.wga.common.ImmutableObject;
import eu.medsea.mimeutil.MimeUtil;

/**
* Parent class for all document objects in a WGA database.
*/
public abstract class WGDocument implements Lockable, WGExtensionDataContainer {
 
  public static final String CSFEATURE_EXTDATA = "Extension data";

    static MetaConverter TITLE_CONVERTER = new MetaConverter() {
    public Object convert(WGDocument doc, MetaInfo metaInfo, Object value) throws WGAPIException {
      String title = (String) value;
      if (title != null) {
        title = WGUtils.strReplace(title, "\n", "", true);
        title = WGUtils.strReplace(title, "\r", "", true);
        title = WGUtils.strReplace(title, "\t", "", true);
      }
      return title;
    }       
  };
   
    //  Metadata available for all documents
    public static final String META_CREATED = "CREATED";
    public static final MetaInfo METAINFO_CREATED = new MetaInfo(META_CREATED, Date.class, new Date(Long.MIN_VALUE));
   
    public static final String META_LASTMODIFIED = "LASTMODIFIED";
    public static final MetaInfo METAINFO_LASTMODIFIED = new MetaInfo(META_LASTMODIFIED, Date.class, new Date(Long.MIN_VALUE));
    static { METAINFO_LASTMODIFIED.addSynonym("MODIFIED"); };

    public static final String META_REVISION = "REVISION";
    public static final MetaInfo METAINFO_REVISION = new MetaInfo(META_REVISION, Integer.class, new Integer(0));
    static { METAINFO_REVISION.setLuceneIndexType(MetaInfo.LUCENE_INDEXTYPE_NOINDEX); };
   
    public static final String META_PASTAUTHORS = "PASTAUTHORS";
    public static final MetaInfo METAINFO_PASTAUTHORS = new MetaInfo(META_PASTAUTHORS, String.class, Collections.EMPTY_LIST);
    static { METAINFO_PASTAUTHORS.setMultiple(true); METAINFO_PASTAUTHORS.setLuceneIndexType(MetaInfo.LUCENE_INDEXTYPE_NOINDEX); };

    public static final String META_PASTEDITDATES = "PASTEDITDATES";
    public static final MetaInfo METAINFO_PASTEDITDATES = new MetaInfo(META_PASTEDITDATES, Date.class, Collections.EMPTY_LIST);
    static { METAINFO_PASTEDITDATES.setMultiple(true); METAINFO_PASTEDITDATES.setLuceneIndexType(MetaInfo.LUCENE_INDEXTYPE_NOINDEX); };



    private Date objectCreated;

    private boolean temporary;

    public static final String DOCKEY_DIVIDER = "/";

    // Document types
    /**
     * Document types of WGContent objects
     */
    public static final int TYPE_CONTENT = 1;

    /**
     * Document types of WGStructEntry objects
     */
    public static final int TYPE_STRUCTENTRY = 2;

    /**
     * Document types of WGArea objects
     */
    public static final int TYPE_AREA = 3;

    /**
     * Document types of WGLanguage objects
     */
    public static final int TYPE_LANGUAGE = 4;

    /**
     * Document types of WGContentType objects
     */
    public static final int TYPE_CONTENTTYPE = 5;

    /**
     * Document types of WGTMLModule objects
     */
    public static final int TYPE_TML = 6;

    /**
     * Document types of WGCSSJSModule objects
     */
    public static final int TYPE_CSSJS = 7;

    /**
     * Document types of WGFileContainer objects
     */
    public static final int TYPE_FILECONTAINER = 8;

    /**
     * Document types of WGUserProfile objects
     */
    public static final int TYPE_USERPROFILE = 9;
   
   
    /**
     * Pseudo document type for ACL entries
     */
    public static final int TYPE_ACLENTRY = 101;

    /**
     * Pseudo document type for database metadata (i.e. extension data on db level)
     */
    public static final int TYPE_DBMETADATA = 102;
   
    // Document type names
    /**
     * Document type name of WGContent objects
     */
    public static final String TYPENAME_CONTENT = "content";

    /**
     * Document type name of WGStructEntry objects
     */
    public static final String TYPENAME_STRUCTENTRY = "structentry";

    /**
     * Document type name of WGArea objects
     */
    public static final String TYPENAME_AREA = "area";

    /**
     * Document type name of WGLanguage objects
     */
    public static final String TYPENAME_LANGUAGE = "language";

    /**
     * Document type name of WGContentType objects
     */
    public static final String TYPENAME_CONTENTTYPE = "contenttype";

    /**
     * Document type name of WGTMLModule objects
     */
    public static final String TYPENAME_TML = "tml";

    /**
     * Document type name of WGCSSJSModule objects
     */
    public static final String TYPENAME_CSSJS = "cssjs";

    /**
     * Document type name of WGFileContainer objects
     */
    public static final String TYPENAME_FILECONTAINER = "filecontainer";

    /**
     * Document type name of WGUserProfile objects
     */
    public static final String TYPENAME_USERPROFILE = "userprofile";
   
    /**
     * Pseudo document type name for ACL entries
     */
    public static final String TYPENAME_ACLENTRY = "$aclentry";
   
    /**
     * Pseudo document type name for database metadata
     */
    public static final String TYPENAME_DBMETADATA = "$dbmetadata";

    /**
     * Prefix for names of extension data fields that actually store document metadata fields
     */
    public static final String EXTDATA_META_PREFIX = "meta_";
   
    /**
     * extension data field which stores the "primary attachment" for this document in CS Versions > 5
     */
    public static final String EXT_PRIMARY_ATTACHMENT = "primaryAttachment";

    // Static mappings from doctype name to doctype number
    private static Map<String,Integer> docTypeNameToNumber = new HashMap<String,Integer>();

    private static Map<Integer,String> docTypeNumberToName = new HashMap<Integer,String>();
   
    private static Map<Integer,Class<?>> docTypeNumberToClass = new HashMap<Integer,Class<?>>();

    // Unique key mappings in debug mode only
    protected static Map uniqueKeys = Collections.synchronizedMap(new HashMap());
   
    // Initialisations of mappings
    static {

        // Mappings from doctype name to number and vice versa
        docTypeNameToNumber.put(TYPENAME_CONTENT, new Integer(TYPE_CONTENT));
        docTypeNameToNumber.put(TYPENAME_STRUCTENTRY, new Integer(TYPE_STRUCTENTRY));
        docTypeNameToNumber.put(TYPENAME_AREA, new Integer(TYPE_AREA));
        docTypeNameToNumber.put(TYPENAME_LANGUAGE, new Integer(TYPE_LANGUAGE));
        docTypeNameToNumber.put(TYPENAME_CONTENTTYPE, new Integer(TYPE_CONTENTTYPE));
        docTypeNameToNumber.put(TYPENAME_TML, new Integer(TYPE_TML));
        docTypeNameToNumber.put(TYPENAME_CSSJS, new Integer(TYPE_CSSJS));
        docTypeNameToNumber.put(TYPENAME_FILECONTAINER, new Integer(TYPE_FILECONTAINER));
        docTypeNameToNumber.put(TYPENAME_USERPROFILE, new Integer(TYPE_USERPROFILE));
        docTypeNameToNumber.put(TYPENAME_ACLENTRY, new Integer(TYPE_ACLENTRY));
        docTypeNameToNumber.put(TYPENAME_DBMETADATA, new Integer(TYPE_DBMETADATA));
        docTypeNameToNumber = Collections.unmodifiableMap(docTypeNameToNumber);

        docTypeNumberToName.put(new Integer(TYPE_CONTENT), TYPENAME_CONTENT);
        docTypeNumberToName.put(new Integer(TYPE_STRUCTENTRY), TYPENAME_STRUCTENTRY);
        docTypeNumberToName.put(new Integer(TYPE_AREA), TYPENAME_AREA);
        docTypeNumberToName.put(new Integer(TYPE_LANGUAGE), TYPENAME_LANGUAGE);
        docTypeNumberToName.put(new Integer(TYPE_CONTENTTYPE), TYPENAME_CONTENTTYPE);
        docTypeNumberToName.put(new Integer(TYPE_TML), TYPENAME_TML);
        docTypeNumberToName.put(new Integer(TYPE_CSSJS), TYPENAME_CSSJS);
        docTypeNumberToName.put(new Integer(TYPE_FILECONTAINER), TYPENAME_FILECONTAINER);
        docTypeNumberToName.put(new Integer(TYPE_USERPROFILE), TYPENAME_USERPROFILE);
        docTypeNumberToName.put(new Integer(TYPE_ACLENTRY), TYPENAME_ACLENTRY);
        docTypeNumberToName.put(new Integer(TYPE_DBMETADATA), TYPENAME_DBMETADATA);
        docTypeNumberToName = Collections.unmodifiableMap(docTypeNumberToName);
       
        docTypeNumberToClass.put(new Integer(TYPE_CONTENT), WGContent.class);
        docTypeNumberToClass.put(new Integer(TYPE_STRUCTENTRY), WGStructEntry.class);
        docTypeNumberToClass.put(new Integer(TYPE_AREA), WGArea.class);
        docTypeNumberToClass.put(new Integer(TYPE_LANGUAGE), WGLanguage.class);
        docTypeNumberToClass.put(new Integer(TYPE_CONTENTTYPE), WGContentType.class);
        docTypeNumberToClass.put(new Integer(TYPE_TML), WGTMLModule.class);
        docTypeNumberToClass.put(new Integer(TYPE_CSSJS), WGCSSJSModule.class);
        docTypeNumberToClass.put(new Integer(TYPE_FILECONTAINER), WGFileContainer.class);
        docTypeNumberToClass.put(new Integer(TYPE_USERPROFILE), WGUserProfile.class);
        docTypeNumberToClass.put(new Integer(TYPE_ACLENTRY), WGACLEntry.class);
        docTypeNumberToClass = Collections.unmodifiableMap(docTypeNumberToClass);
    }

    /**
     * Returns the meta information for a given metadata field.
     * @param metaName The name of the metadate field to return information for
     * @return returns the MetaInfo for given metaName of this class
     * @throws WGSystemException if the metaDataFramework cannot be accessed
     */
    public MetaInfo getMetaInfo(String metaName) throws WGSystemException {
        return WGFactory.getInstance().getMetaInfo(metaName, getClass());
    }
   
    /**
     * returns the metaInfo of the given metaName for the given doctype
     * @param type
     * @param metaName
     * @return MetaInfo
     * @throws WGSystemException if metaDataFramework access fails
     * @throws WGIllegalArgumentException if type or metaName are invalid
     */
    private static MetaInfo getMetaInfoByType(int type, String metaName) throws WGSystemException, WGIllegalArgumentException {
        Class typeClass = (Class) docTypeNumberToClass.get(new Integer(type));
       
        if (typeClass == null) {
            throw new WGIllegalArgumentException("Class for doctype number '" + type + "' not found.");
        }
        MetaInfo info = WGFactory.getInstance().getMetaInfo(metaName, typeClass);
        if (info == null) {
            throw new WGIllegalArgumentException("Unable to find metainfo for doctype '" + type + "' and name '" + metaName + "'.");           
        } else {
            return info;
        }
       
    }
   
    /**
     * Returns the expected data type of a metadata field
     * @param type Document type for the metadata field
     * @param metaName Name of the metadata field
     * @return Expected data type of the metadata as class object
     * @throws WGIllegalArgumentException
     * @throws WGSystemException
     */
    public static Class getExpectedMetaClass(int type, String metaName) throws WGSystemException, WGIllegalArgumentException {
       
        MetaInfo info = getMetaInfoByType(type, metaName);
        return info.getDataType();
       
    }

    /**
     * Determines if the given metadata field should return lists or single values.
     * @param type Document type of metadata field
     * @param name Name of metadata field
     * @return True, if the metadata field should return lists, false if it shouls return single values
     * @throws WGIllegalArgumentException
     * @throws WGSystemException
     */
    public static boolean isListMeta(int type, String name) throws WGSystemException, WGIllegalArgumentException {

        MetaInfo info = getMetaInfoByType(type, name);
        return info.isMultiple();
       
    }
   
    class UniqueNameTester implements Runnable {

        private String language;
        private Object structKey;
        public boolean isUniqueNameOK = false;
        private String uniqueName = null;
       
        public UniqueNameTester(String uniqueName, String language, Object structKey) {
            this.uniqueName = uniqueName;
            this.language = language;
            this.structKey = structKey;
        }

        public void run() {
            if (this.uniqueName == null || this.uniqueName.equals("")) {
                this.isUniqueNameOK = true;
                return;
            }
            if (this.structKey == null) {
                return;
            }
            WGDatabase db = getDatabase();
            
            try {
                db.openSession();
                WGContent existingContent = null;
               
                if (language != null) {
                    existingContent = db.getContentByName(this.uniqueName, language);
                }
                else {
                    existingContent = db.getAnyContentByName(this.uniqueName);
                }

                if (existingContent == null) {
                    this.isUniqueNameOK = true;
                }
                else if (structKey.equals(existingContent.getStructKey())) {
                    this.isUniqueNameOK = true;
                }
            }
            catch (WGAPIException e) {
            }
            finally {
                WGFactory.getInstance().closeSessions();
            }

        }
    }
   
    /**
     * checks all metas of this document to ensure value and datatype correspond to the current
     * api implementation. Parameter autoCorrection can be used to automatically correct wrong
     * metadata instead of throwing an exception.
     * @throws WGAPIException
     */
    public boolean validateMetas(boolean autoCorrection) throws WGAPIException {
       
        boolean somethingCorrected = false;
        Iterator metaNames = getMetaNames().iterator();
       
        while (metaNames.hasNext()) {
            String name = (String) metaNames.next();
            MetaInfo metaInfo = this.getMetaInfo(name);
            if (metaInfo.getMinCsVersion() > getDatabase().getContentStoreVersion()) {
                continue;
            }
           
            Object value = this.getCore().getMetaData(name);
            try {
                // check output conversions (meta can be read from backend)
                value = metaInfo.convertOutput(this, value);
                // check for input (checks also allowed values)
                metaInfo.convertInput(this, value);
            }
            catch (WGAPIException e) {
                if (autoCorrection) {
                    setMetaToDefault(name);
                    somethingCorrected = true;
                }
                else {
                    throw e;
                }
            }
        }
       
        return somethingCorrected;
    }
   
    /**
     * Variant of {@link #validateMetas(boolean)} that performs no auto correction.
     * @return true, if the metadata is valid
     * @throws WGAPIException
     */
    public boolean validateMetas() throws WGAPIException {
       
        return validateMetas(false);
       
    }
   
    /**
     * sets the given meta to the default value
     * @param metaName
     * @throws WGAPIException
     */
    public void setMetaToDefault(String metaName) throws WGAPIException {
        MetaInfo metaInfo = getMetaInfo(metaName);
        setMetaData(metaName, metaInfo.getDefaultValue());
    }
   
    /**
     * Builds the document key for a document core.
     * @param core
     * @throws WGAPIException
     */
    public static WGDocumentKey buildDocumentKey(WGDocumentCore core, WGDatabase db) throws WGAPIException {

        String name = null;
        String mediaKey = null;
        switch (core.getType()) {

            case WGDocument.TYPE_CONTENT:
                name = String.valueOf(WGContentKey.create(core, true));
                break;

            case WGDocument.TYPE_AREA:
            case WGDocument.TYPE_CONTENTTYPE:
            case WGDocument.TYPE_FILECONTAINER:
            case WGDocument.TYPE_LANGUAGE:
                name = (String) core.getMetaData(WGDesignDocument.META_NAME);
                break;

            case WGDocument.TYPE_TML:
                name = (String) core.getMetaData(WGDesignDocument.META_NAME);
                mediaKey = (String) core.getMetaData(WGTMLModule.META_MEDIAKEY);
                break;
               
            case WGDocument.TYPE_CSSJS:
                name = (String) core.getMetaData(WGDesignDocument.META_NAME);
                // Let's see if generally extending script module keys breaks anything...
                /*if (db.getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {*/
                    mediaKey = (String) core.getMetaData(WGCSSJSModule.META_CODETYPE);                   
                /*}*/


            case WGDocument.TYPE_USERPROFILE:
                name = String.valueOf(core.getMetaData(WGUserProfile.META_NAME));
                break;

            case WGDocument.TYPE_STRUCTENTRY:
                name = String.valueOf(core.getMetaData(WGStructEntry.META_KEY));
                break;

        }

        return new WGDocumentKey(core.getType(), name, mediaKey);

    }   

    protected WGDatabase db = null;

    private Object fastAccessKey = null;

    private WGDocumentKey uniqueId = null;

    private boolean cachingEnabled = false;

    private boolean _deleted = false;

    private Set<String> dontCacheTheseItems = new CopyOnWriteArraySet<String>();

    protected Set<String> dontCacheTheseMetas = new CopyOnWriteArraySet<String>();

    private Map persistentStore = Collections.synchronizedMap(new HashMap());

    private String originDatabase;

    /**
     * (Re-)Retrieves the core object for this document.
     * @throws WGAPIException
     */
    protected abstract WGDocumentCore retrieveCore() throws WGAPIException;

    /**
     * Returns the document key of this document, which is unique in this
     * database among all documents.
     */
    public String getDocumentKey() {
        return uniqueId.toString();
    }
   
    /**
     * Returns a parsed document key object, ready to access single parts of the key
     */
    public WGDocumentKey getDocumentKeyObj() {
        return uniqueId;
    }

    /**
     * Converts doctype names WGDocument.TYPENAME_... to number constants
     * WGDocument.TYPE_...
     *
     * @param name
     */
    public static int doctypeNameToNumber(String name) {

        Integer number = (Integer) docTypeNameToNumber.get(name);
        if (number != null) {
            return number.intValue();
        }
        else {
            return 0;
        }
    }
   
    public static Class doctypeNumberToClass(int number) {
        return (Class) docTypeNumberToClass.get(number);
    }

    /**
     * Converts doctype numbers WGDocument.TYPE_... to doctype names
     * WGDocument.TYPENAME_...
     *
     * @param number
     */
    public static String doctypeNumberToName(int number) {
        return (String) docTypeNumberToName.get(new Integer(number));
    }

    /**
     *  Drops the cache of this document.
     * @throws WGAPIException
     */
    public void dropCache() throws WGAPIException {
        db.getCache().flushDocumentCache(this);
        this.dontCacheTheseItems.clear();
        this.dontCacheTheseMetas.clear();
        this.persistentStore.clear();
        this.dontCacheTheseMetas.add(WGContent.META_SEARCHSCORE);
    }

    protected void dispose() throws WGAPIException {

        disposed = true;
       
        dropCache();

        //    Document will be dropped by WGDatabase. So it is not allowed to cache
        // anymore bc. noone will inform it to drop cache again.
        this.setCachingEnabled(false);
    }

    /**
     * Constructor.
     * @param db
     * @param core
     * @throws WGAPIException
     */
    public WGDocument(WGDatabase db, WGDocumentCore core) throws WGAPIException {
       
        this.fastAccessKey = core.getFastAccessKey();
        this.uniqueId = buildDocumentKey(core, db);
       
        this.db = db;
        this.setCore(core);
        this.objectCreated = new Date();

        this.cachingEnabled = (core.isDataCacheable() && db.getSessionContext().isCachingEnabled() && db.isDoctypeCacheable(getType()));
        this.temporary = core.isTemporary();
        this.originDatabase = core.getOriginDatabase();

        if (WGDatabase.VERBOSE_DOCUMENT_INSTANTIATION) {
            String className = this.getClass().getName();
            if (className.lastIndexOf(".") != -1) {
                className = className.substring(className.lastIndexOf(".") + 1);
            }
            WGFactory.getLogger().info("Document instantiation DB: " + db.getDbReference() +
                    " - Class: " + className +
                    " - Key: "+ this.uniqueId + (this.isTemporary() ? " (temporary)" : "") +
                    " - Cache: " + (!getDatabase().getSessionContext().isCachingEnabled() ? "disabled" : !getDatabase().getSessionContext().isCacheWritingEnabled() ? "readonly" : "enabled") +
                    " - Session: " + getDatabase().getSessionContext().hashCode());
        }
      
        // May init some caches, that need to be always available
        dropCache();
       
        // Update caches that should reflect unmodified backend values
        updateBackendCaches(core);
       
    }



    /**
     * Returns a text item.
     * If the item is not of type text it returns the text representation (obj.toString())
     *
     * @param strName Name of the item
     * @throws WGAPIException
     */
    public String getItemText(String strName) throws WGAPIException {

        if (!hasItem(strName)) {
            return getDatabase().getNoItemBehaviour().getForGetItemText();
        }
       
        Object value = this.getItemValue(strName);
        if (value == null) {
            return null;
        }

        if (value instanceof String) {
            return (String) value;
        }
        if (value instanceof List) {
            List valueList = (List) value;
            if (valueList.size() >= 1) {
                return String.valueOf(valueList.get(0));
            }
            else {
                return null;
            }
        }
        else {
            return String.valueOf(value);
        }
    }

    /**
     * Returns an item's value.
     * If the item does not exist it returns null by default. You can modify this behaviour
     * by database option "NoItemValue".
     *
     * @param strName
     *            Name of the item
     * @throws WGAPIException
     */
    public Object getItemValue(String strName) throws WGAPIException {
       
        if (!getDatabase().isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        Object value = null;
       
        Map cache = null;
        boolean cacheable = isCachingEnabled() && !this.dontCacheTheseItems.contains(strName);
        if (cacheable) {
            cache = db.getCache().readItemCache(this);
            if (cache == null) {
                cache = WGUtils.createSynchronizedMap();
            }
        }
       
        String cacheKey = "itemvalue:" + strName;
        if (cache != null) {
      value = cache.get(cacheKey);
        }

        if (value == null) {
            WGDocumentCore theCore = this.getCore();
            if (theCore.hasItem(strName)) {
                value = theCore.getItemValue(strName);
            }
            else {
                value = getDatabase().getNoItemBehaviour().getForGetItemValue();
            }
           
            value = cloneMutableObjects(value);
           
            if (cache != null && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                cache.put(cacheKey, nullPlaceholder(value));
                db.getCache().writeItemCache(this, cache);
            }
        }
        else if (value instanceof NullPlaceHolder) {
            value = null;
        }
       
        // Return only copies of mutable objects, so modifying them will not modify the cache
        value = cloneMutableObjects(value);

        return value;

    }

    protected Object cloneMutableObjects(Object value) {
       
        if (value == null) {
            return null;
        }
       
        if (value instanceof Number ||
                value instanceof String ||
                value instanceof Boolean) {
            // Do nothing. We have classical immutable objects.
        }
       
        else if (value instanceof ImmutableObject) {
            // Do nothing. The object was flagged to be immutable
        }
       
        // Clone objects with their native functionalities
        else if (value instanceof Date) {
            return new Date(((Date) value).getTime());
        }
        else if (value instanceof List) {
            value = new ArrayList((List) value);
        }
        else if (value instanceof Set) {
            value = new HashSet((Set) value);
        }
        else if (value instanceof Map) {
            value = new HashMap((Map) value);
        }
        else {
            // We have some unknown object that may be mutable.
            // We will try to clone it using XStream
            getDatabase().verboseMutableCloning(value);
            Object clone = XStreamUtils.clone(value);
            if (clone != null) {
                value = clone;
            }
            else {
                WGFactory.getLogger().warn("Object of type '" + value.getClass().getName() + "' cannot be cloned from cache. Modifying this object will influence the cached values.");
            }
        }
       
        return value;
    }

    /**
     * Returns an items value(s) always as list. If the item is not multivalue, this
     * method returns a list containing only the single value.
     *
     * @param name
     *            The item's name
     * @throws WGAPIException
     */
    public List getItemValueList(String name) throws WGAPIException {

        if (!hasItem(name)) {
            return getDatabase().getNoItemBehaviour().getForGetItemValueList();
        }
       
        Object value = this.getItemValue(name);
        if (value instanceof List) {
            return (List) value;
        }
        else {
            List list = new ArrayList();
            if (value != null) {
                list.add(value);
            }
            return list;
        }

    }

    /**
     * Retrieves a number item.
     * If the item is not a number returns null.
     *
     * @param strName
     *            Name of the item.
     * @throws WGAPIException
     */
    public Number getItemNumber(String strName) throws WGAPIException {

        if (!hasItem(strName)) {
            return null;
        }
       
        Object numberObj = this.getItemValue(strName);
        if (numberObj instanceof List) {
            List numberList = (List) numberObj;
            if (numberList.size() >= 1) {
                numberObj = numberList.get(0);
            }
            else {
                numberObj = null;
            }
        }

        if (numberObj != null && numberObj instanceof Number) {
            return (Number) numberObj;
        }
        else {
            return null;
        }
    }

    /**
     * Retrieves the value of a date item.
     * If the item is not of type date it returns null.
     *
     * @param strName
     *            Name of the item
     * @throws WGAPIException
     */
    public Date getItemDate(String strName) throws WGAPIException {

        if (!hasItem(strName)) {
            return null;
        }
       
        Object dateObj = this.getItemValue(strName);
        if (dateObj instanceof List) {
            List dateList = (List) dateObj;
            if (dateList.size() >= 1) {
                dateObj = dateList.get(0);
            }
            else {
                dateObj = null;
            }
        }

        if (dateObj != null && dateObj instanceof Date) {
            return (Date) dateObj;
        }
        else {
            return null;
        }
    }

    /**
     * Sets an item to the given value. If the item doesn't exist, it is
     * created. Otherwise it's value gets overwritten.
     *
     * @param strName
     *            Name of the item
     * @param value
     *            Value of the item. Accepted values are dependent to the
     *            database implementation
     * @return True, if the setting succeeded, false otherwise.
     * @throws WGAPIException 
     */
    public boolean setItemValue(String strName, Object value) throws WGAPIException {

        this.dontCacheTheseItems.add(strName);
               
        // check for NaN
        if (value != null) {
            if (value instanceof Number && ((Number)value).equals(new Double(Double.NaN))) {
                throw new WGIllegalArgumentException("NaN is not a valid value for item '" + strName + "'.");
            }           
        }
               
        boolean result = this.getCore().setItemValue(strName, this.parseValue(value));
        if (result == true) {
            setEdited(true);
        }
        return result;
    }

    /**
     * Method parseValue.
     *
     * @param value
     * @return Object
     */
    private Object parseValue(Object value) {

        if (this.db.hasFeature(WGDatabase.FEATURE_COMPLEXVALUES)) {
            return value;
        }
        else if (value == null) {
            return "";
        }
        else if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof java.util.Date
                || value instanceof Collection) {
            return value;
        }
        else if (value instanceof Object[]) {
            return Arrays.asList((Object[]) value);
        }
        else {
            return value.toString();
        }

    }

    /**
     * Sets a meta data of this document. Since there are "direct" methods for
     * any metadata on the document subclasses, WGAPI users should not use this
     * method directly, but prefer the direct methods. E.g. Use
     * WGContent.setTitle("Home") instead of
     * WGDocument.setMetaData(WGContent.META_TITLE, "Home")
     *
     * @param strName
     *            Name of the meta data. Use constants META_... of WGDocument
     *            and all descendant objects.
     * @param value
     *            Value to set this metadata
     * @return True, if the setting succeeded, false otherwise
     * @throws WGAPIException 
     */
    public boolean setMetaData(String strName, Object value) throws WGAPIException {
        this.dontCacheTheseMetas.add(strName);

        MetaInfo info = getMetaInfo(strName);
        if (info == null) {
            throw new WGIllegalDataException("Metadata field '" + strName + "' not defined for document type '" + WGDocument.doctypeNumberToName(getType()) + "'");
        }
       
        double csVersion = getDatabase().getContentStoreVersion();
        if (info.getMinCsVersion() > csVersion) {
            throw new WGContentStoreVersionException("Metadata field " + strName, info.getMinCsVersion());
        }
       
        value = info.convertInput(this, value);           
        if (info.isExtdataInCsVersion(csVersion)) {
            writeExtensionData(EXTDATA_META_PREFIX + strName, value);
        }
        else {
            this.getCore().setMetaData(strName, value);
        }
       
        setEdited(true);
        return true;
    }



    /**
     * Saves the current document with it's modifications.
     * Special version only to be used inside WGAPI, e.g. for createClone.
     *
     * @param lastModified Date of last modification to set (if this is supported by the implementation)
     * @return true, if the saving succeded, false otherwise.
     * @throws WGAuthorisationException
     * @throws WGValidationException
     * @throws LockException
     *         - instance of ResourceIsLockedException if a process try to modifiy a locked object without obtaining a lock 
     * @throws WGSystemException
     * @throws WGIllegalArgumentException
     */
    public synchronized boolean save(Date lastModified) throws WGAPIException {

        performSaveCheck();
       
        // Check origin. If from design provider we should not update lastChanged
        boolean designProviderCore = false;
        WGDocumentCore docCore = getCore();
        if (db.getDesignProvider() != null && db.getDesignProvider().isProviderCore(docCore)) {
            designProviderCore = true;
        }


        boolean isNewDoc = !docCore.isSaved();
        WGSessionContext sessionContext = getDatabase().getSessionContext();

        synchronized (db) {
           
            // We will update the last changed date only, when the core is from this database
            // and there are no pending changes in background
            boolean updateLastChanged = false;
            if (!designProviderCore) {
                updateLastChanged = !getDatabase().isDatabaseUpdatedInBackground();
            }
           
            boolean result = docCore.save(lastModified);

            if (result == true) {
                setEdited(false);

                // This block necessary, if a document goes from temporary to
                // persistent
                this.temporary = docCore.isTemporary();

                if (isNewDoc) {
                   
                    // In case, some parts of the key were generated on first save
                    // Note: Implementations that do this MUST NOT support transactions on WGAPI level!
                    WGDocumentKey oldId = uniqueId;
                    uniqueId = buildDocumentKey(docCore, getDatabase());
                    if (!oldId.equals(uniqueId)) {
                        getDatabase().getSessionContext().remapDocumentContext(oldId, uniqueId);
                    }
                   
                    // Replace doc and core in current document context, because there still may be "dummy" instances of these (B00005D32)
                    DocumentContext docContext = getDocumentSessionContext();
                    docContext.setDocument(this);
                    docContext.setCore(docCore);
                }

                if (!sessionContext.isTransactionActive()) {
                    dropCache();
                    fastAccessKey = docCore.getFastAccessKey();
                    db.documentSaved(this, getDocumentKeyObj(), isNewDoc, true, updateLastChanged);
                    updateBackendCaches(docCore);
                }
            }
            return result;
        }
    }

    /**
     * Performs a validation for saving the document at the current state, just like it actually was saved.
     * This method finishes quietly if the save check succeeded. Otherwise an adequate
     * {@link WGAuthorisationException} is thrown.
     * For checking saving permissions without catching an exception see {@link #maySave()}.
     * @throws WGAPIException
     */
    public void performSaveCheck() throws WGAuthorisationException, WGAPIException, ResourceIsLockedException {
        // Check access level
        if (db.getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_AUTHOR) {
            if (this instanceof WGUserProfile) {
                if (!(db.getSessionContext().getAccessLevel() >= WGDatabase.ACCESSLEVEL_READER && getDatabase().isReaderProfileCreation())) {
                    throw new WGAuthorisationException("You are not authorized to save this document");   
                }
            }
            else {
                throw new WGAuthorisationException("You are not authorized to save this document");
            }
        }
       
        // check lock
        if (getLockStatus() == Lock.LOCKED_BY_FOREIGN_OBJECT) {
            throw new ResourceIsLockedException("The document you try to save is locked. You should obtain the lock first by using 'document.lock()'. Saving aborted.");
        }
    }
   
    /**
     * Tests if the current user is allowed to save the current document
     * For getting more details about failure reasons for save checks see {@link #performSaveCheck()}
     * @throws WGAPIException
     */
    public boolean maySave() throws WGAPIException {
       
        try {
            performSaveCheck();
            return true;
        }
        catch (WGAuthorisationException e) {
            return false;
        }
        catch (WGIllegalStateException e) {
            return false;
        }
       
    }
   
    /**
     * Tests if the current user is allowed to remove the current document
     * For getting more details about failure reasons for remove checks see {@link #performRemoveCheck()}
     * @throws WGAPIException
     */
    public boolean mayRemove() throws WGAPIException {
       
        try {
            performRemoveCheck();
            return true;
        }
        catch (WGAuthorisationException e) {
            return false;
        }
        catch (WGIllegalStateException e) {
            return false;
        }
       
    }

    /**
     * Saved the current state of the document.
     * @throws WGAPIException
     */
    public boolean save() throws WGAPIException {
        return save(new Date());
    }

    /**
     * Evaluates a native expression with the current document as context.
     *
     * @param expression
     *            The expression
     * @return The expression result.
     * @throws WGAPIException
     */
    public Object evaluateExpression(String expression) throws WGAPIException {

        Object result = getCore().evaluateExpression(expression);
        return result;

    }

    /**
     * Returns the database that stores this document
     */
    public WGDatabase getDatabase() {
        return this.db;
    }

    /**
     * Returns the type of this document as constant WGDocument.TYPE_...
     */
    public abstract int getType();

    /**
     * Returns meta data about this document. Use constants META_... of
     * WGDocument and all descendant objects. Since there are "direct" methods
     * for any metadata on the document subclasses, WGAPI users should not use
     * this method directly, but prefer the direct methods. E.g. Use
     * WGContent.getTitle() instead of
     * WGDocument.getMetaData(WGContent.META_TITLE)
     *
     * @return The meta data.
     * @throws WGAPIException
     */
    public Object getMetaData(String name) throws WGAPIException {
       
        if (!getDatabase().isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        name = name.toUpperCase();
       
        // try to retrieve from cache
        Object value = null;
        Map cache = null;
        MetaInfo metaInfo = getMetaInfo(name);
        boolean cacheable = isCachingEnabled() && !this.dontCacheTheseMetas.contains(name) && (metaInfo == null || metaInfo.isCacheable());
        if (cacheable) {
            cache = db.getCache().readMetaCache(this);
            if (cache == null) {
                cache = WGUtils.createSynchronizedMap();
            }
        }       
        String cacheKey = "metavalue:" + name;
        if (cache != null) {
            value = cache.get(cacheKey);
        }
              
        // if not in cache retrieve from backend
        if (value == null) {
            if (metaInfo != null) {
               
                // Unsupported metadata for this cs version are returned as default value
                double csVersion = getDatabase().getContentStoreVersion();
                if (metaInfo.getMinCsVersion() > csVersion) {
                    value = metaInfo.getDefaultValue();
                }
                else {
                   
                    // Read metadata from attribute or as core meta
                    if (metaInfo.isExtdataInCsVersion(csVersion)) {
                        value = getExtensionData(EXTDATA_META_PREFIX + name);
                    }
                    else {
                        value = this.getCore().getMetaData(name);
                    }
         
                    value = metaInfo.convertOutput(this, value);
                }

            }
           
            // Fallback for unregistered metas
            else {
                value = this.getCore().getMetaData(name);
            }

            // cache
      if (cache != null && getDatabase().getSessionContext().isCacheWritingEnabled()) {   
                value = cloneMutableObjects(value);
              cache.put(cacheKey, nullPlaceholder(value));
        db.getCache().writeMetaCache(this, cache);
            }
        }
        else if (value instanceof NullPlaceHolder) {
            value = null;
        }

        value = cloneMutableObjects(value);

        return value;
    }

    /**
     * @param value
     */
    protected Object nullPlaceholder(Object value) {
        return (value != null ? value : new NullPlaceHolder());
    }

    /**
     * Returns meta data about this document. Use constants META_... of
     * WGDocument and all descendant objects. Since there are "direct" methods
     * for any metadata on the document subclasses, WGAPI users should not use
     * this method directly, but prefer the direct methods. E.g. Use
     * WGContent.getTitle() instead of
     * WGDocument.getMetaData(WGContent.META_TITLE)
     *
     * @return The meta data as list.
     * @throws WGAPIException
     */
    public List getMetaDataList(String name) throws WGAPIException {

        Object value = this.getMetaData(name);
        if (value instanceof List) {
            return (List) value;
        }
        else {
            return new ArrayList(Collections.singletonList(value));
        }

    }

    /**
     * Returns the creation date of this document
     * @throws WGAPIException
     */
    public java.util.Date getCreated() throws WGAPIException {
        return (Date) this.getMetaData(WGDocument.META_CREATED);
    }

    /**
     * Returns the last modification date of the document
     * @throws WGAPIException
     */
    public java.util.Date getLastModified() throws WGAPIException {
        return (Date) this.getMetaData(WGDocument.META_LASTMODIFIED);
    }

    /**
     * Returns the core implementation object for this document.
     *
     * @return Returns a WGDocument
     * @throws WGAPIException
     */
    public WGDocumentCore getCore() throws WGAPIException {
       
        if (!db.isSessionOpen()) {
            throw new WGClosedSessionException();
        }

        WGDocumentCore core = (WGDocumentCore) getDocumentSessionContext().getCore();
        if (core == null) {
            if (this.fastAccessKey != null) {
                getDatabase().verboseBackendAccess(WGOperationKey.OP_DOCUMENT_CORE_FASTACCESS, this.fastAccessKey);
                core = this.db.getCore().fastAccess(getType(), this.fastAccessKey);
               
                // Look if design document got renamed
                if (core != null && this instanceof WGDesignDocument) {
                    WGDocumentKey newCoreKey = WGDocument.buildDocumentKey(core, getDatabase());
                    if (!newCoreKey.equals(uniqueId)) {
                        // Document got renamed. Drop that one, including it's fast access key. Retrieve new.
                        core = null;
                        fastAccessKey = null;
                    }
                }
            }

            if (core == null) {
                getDatabase().verboseBackendAccess(WGOperationKey.OP_DOCUMENT_CORE, this.uniqueId);
                core = this.retrieveCore();
                if (core != null) {
                    this.fastAccessKey = core.getFastAccessKey();
                }
            }

            if (core != null) {
                setCore(core);
                updateBackendCaches(core);
            }
            else {
               
                // Could not find document, maybe already deleted. Will use WGDocumentFake
                WGFakeDocument fakeDoc = new WGFakeDocument(db, getType());
                fakeDoc.setDeleted(true);
                return fakeDoc;
            }
        }
        return core;
    }

    /**
     * Callback method that gives the document the opportunity to update backend caches,
     * i.e. caches that should reflect unmodified backend data of the document and not modified values.
     * @param core
     */
    protected void updateBackendCaches(WGDocumentCore core) {
    }

    /**
     * Returns if the current documents holds a document core (i.e. a backend document)
     */
    public boolean isCoreRetrieved() {
        return getDocumentSessionContext().isCoreRetrieved();
    }

    /**
     * Sets the document core.
     *
     * @param core
     *            The doc to set
     */
    protected void setCore(WGDocumentCore core) {
        if (core != null) {
            getDatabase().getSessionContext().addFetchedCore(this, core);
            core.setWGDocument(this);
        }
    }

    /**
     * Tests, if the given native expression result means "true" for this
     * database implementation.
     */
    public boolean resultIsTrue(Object exprResult) {

        if (exprResult == null) {
            return false;
        }

        return db.getCore().resultIsTrue(exprResult, this);
    }

    /**
     * Tests, if the given native expression result means "false" for this
     * database implementation.
     *
     * @param exprResult
     */
    public boolean resultIsFalse(Object exprResult) {

        if (exprResult == null) {
            return false;
        }

        return db.getCore().resultIsFalse(exprResult, this);
    }

    /**
     * Tests if this document object is temporary and will be discarded after
     * the session. This doesn't mean that the database document represented by
     * this object will be deleted. It just means that there will be another
     * document object for the database document in every session that accesses
     * it.
     */
    public boolean isTemporary() {

        return temporary;

    }

    /**
     * Calls the database backend to determine if this document has been
     * deleted, maybe in another session
     * @throws WGAPIException
     */
    public boolean isDeleted() throws WGAPIException {

        boolean result = false;
       
        if (!db.isDeletionCheck() && isCachingEnabled()) {
            return _deleted;
        }
       
        try {
            result = (_deleted || getCore() == null || getCore().isDeleted());
        }
        catch (Exception exc) {
            WGFactory.getLogger().error("Exception determining deletion state of " + getDocumentKey(), exc);
            result = true;
        }
       

        if (result == true) {
            // Try again with freshly fetched core
            dropCore();
            return (_deleted || getCore() == null || getCore().isDeleted());
        }
        else {
            return false;
        }

    }

    protected boolean isDeletedFlag() {
        return _deleted;
    }

    /**
     * Returns a list of file attachment names for this document
     * @throws WGAPIException
     */
    public java.util.List<String> getFileNames() throws WGAPIException {
        return this.getCore().getFileNames();
    }
   
    /**
     * Convenience method to remove all file attachments from the document at once
     * @throws WGAPIException
     */
    public void removeAllFiles() throws WGAPIException {
        Iterator names = getFileNames().iterator();
        while (names.hasNext()) {
            String name = (String) names.next();
            removeFile(name);
        }
    }
   
    /**
     * Copies all attachments of the parameter document to the current document
     * @param doc The document to get attachments from
     * @throws IOException
     * @throws WGAPIException
     */
    public void attachAllFiles(WGDocument doc) throws IOException, WGAPIException {
       
        Iterator names = doc.getFileNames().iterator();
        while (names.hasNext()) {
            String name = (String) names.next();
            TemporaryFile tempFile = new TemporaryFile(name, doc.getFileData(name), WGFactory.getTempDir());           
            attachFile(tempFile.getFile());
            tempFile.deleteOnEviction(getDatabase().getSessionContext());
        }
       
    }

    /**
     * Returns the data of a file attachment as input stream
     * Retrieving the data of a recently attached file while the document is not yet saved is not supported and may produce unknown results.
     * @param strFile
     *            The name of the attachment
     * @throws WGAPIException
     */
    public java.io.InputStream getFileData(String strFile) throws WGAPIException {
        return this.getCore().getFileData(strFile);
    }
   
    /**
     * Returns the meta data object for the given filename
     * @param strFile
     * @return WGFileMetaData
     * @throws WGAPIException
     */
    public WGFileMetaData getFileMetaData(String strFile) throws WGAPIException {
      return getCore().getFileMetaData(strFile);
    }
   
    /**
     * Returns the (primary) mime-type for the file of the given name.
     * This method will try to use a previously stored mimetype from the files {@link WGFileMetaData}. If that does not exist it will fallback to mimetype determination based on file data.
     * @param strFile
     * @return String
     * @throws WGAPIException
     */
    public String getFileMimeType(String strFile) throws WGAPIException{
       
        // Return stored mimetype extdata
        WGFileMetaData fileMetaData = getFileMetaData(strFile);
        if (fileMetaData != null) {
            String mimeType = fileMetaData.getMimeType();
            if (mimeType != null) {
                return mimeType;
            }
        }
       
        // Determine mimetype by data
      Iterator it = getFileMimeTypes(strFile).iterator();
      return it.next().toString();
    }
   
    /**
     * Returns all mime-types for the file of the given name.
     * This method always determines mimetypes based on the file data. For a more performant determination use {@link #getFileMimeType(String)}.
     * @param strFile
     * @return Collection
     * @throws WGAPIException
     */
    public Collection<String> getFileMimeTypes(String strFile) throws WGAPIException{
      InputStream in = getFileData(strFile);
      Collection<String> mimeTypes = MimeUtil.getMimeTypes(in);
    if(in != null){
      try {
        in.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
      return mimeTypes;
    }
   
    /**
     * Returns the contents of a file attachment in text format. Only valid if
     * the file contains textual information.
     *
     * @param strFile
     *            The file attachment
     * @return A reader object for the text information.
     * @throws WGAPIException
     */
    public java.io.Reader getFileText(String strFile) throws WGAPIException {
        return new java.io.InputStreamReader(this.getCore().getFileData(strFile));
    }
    /**
     * Returns the size of an attachment in bytes. Returns -1 if a file of the given name is not available.
     *
     * @param strFile
     *            The file to retrieve size information for
     * @throws WGAPIException
     */
    public int getFileSize(String strFile) throws WGAPIException {
        return this.getCore().getFileSize(strFile);
    }

    /**
     * Provides the best (most specific) information about the last modified date of an individual file attachment.
     * If file metadata is available the date is retrieved from there.
     * Else the last modified date of the whole container is returned.
     * @param file Name of the file attachment
     * @return the most accurate last modified time for that file that is available
     * @throws WGAPIException
     */
    public Date getFileLastModified(String file) throws WGAPIException {
        if (hasFileMetadata()) {
            try {
                WGFileMetaData md = getFileMetaData(file);
                if (md != null && md.getLastmodified() != null) {
                    return md.getLastmodified();
                }
            }
            catch (Exception e) {
                WGFactory.getLogger().error("Error retrieving file last modified from file metadata, file '" + file + "', doc " + getDocumentKey(), e);
            }
        }

        return getLastModified();

    }
   
    /**
     * @see WGDatabaseEventListener#dropCore(WGDatabaseEvent, boolean)
     */
    protected void dropCore(boolean untimelyDispose, boolean force) {

        if (untimelyDispose && isEdited() && !force) {
            return;
        }
       
        getDocumentSessionContext().dropCore(untimelyDispose);
    }

    protected DocumentContext getDocumentSessionContext() {
        return getDatabase().getSessionContext().getOrCreateDocumentContext(this);
    }

    /**
     * Drops the retrieved document core (i.e. backend document), if there is one.
     */
    public void dropCore() {
        dropCore(true, true);
    }

    /**
     * 
     */
    protected abstract void dropRelations();

    /**
     * Returns a map as persistent store for the implementation of this
     * document. An implementation can store data here that is not discarded
     * when a session closes. Not for external use outside the WGAPI.
     *
     * @return Returns a HashMap
     */
    public Map getPersistentStore() {
        return persistentStore;
    }

    /**
     * Retrieves the name information for the given document core. Only works
     * with areas, content (unique name), css/js modules, content types, file
     * containers, languages, struct entries (struct keys) and TML modules
     *
     * @param core
     * @throws WGAPIException
     */
    public static String getName(WGDocumentCore core) throws WGAPIException {

        int type = core.getType();
        if (type == WGDocument.TYPE_AREA) {
            return (String) core.getMetaData(WGArea.META_NAME);
        }
        else if (type == WGDocument.TYPE_CONTENT) {
            return (String) core.getMetaData(WGContent.META_UNIQUE_NAME);
        }
        else if (type == WGDocument.TYPE_CSSJS) {
            return (String) core.getMetaData(WGCSSJSModule.META_NAME);
        }
        else if (type == WGDocument.TYPE_CONTENTTYPE) {
            return (String) core.getMetaData(WGContentType.META_NAME);
        }
        else if (type == WGDocument.TYPE_FILECONTAINER) {
            return (String) core.getMetaData(WGFileContainer.META_NAME);
        }
        else if (type == WGDocument.TYPE_LANGUAGE) {
            return (String) core.getMetaData(WGLanguage.META_NAME);
        }
        else if (type == WGDocument.TYPE_STRUCTENTRY) {
            return (String) core.getMetaData(WGStructEntry.META_KEY);
        }
        else if (type == WGDocument.TYPE_TML) {
            return (String) core.getMetaData(WGTMLModule.META_NAME);
        }
        else {
            return null;
        }
    }

    /**
     * Tests if the document has an item of this name
     * @throws WGAPIException
     */
    public boolean hasItem(String itemName) throws WGAPIException {
       
        Boolean result = null;
        boolean cacheable = isCachingEnabled() && !this.dontCacheTheseItems.contains(itemName);
        Map cache = db.getCache().readItemCache(this);
        if (cache == null && cacheable) {
            cache = WGUtils.createSynchronizedMap();
        }
       
        String cacheKey = "hasitem:" + itemName;
       
        if (cacheable) {
            result = (Boolean) cache.get(cacheKey);
        }
       
        if (result == null) {
            result = new Boolean(getCore().hasItem(itemName));
            if (cacheable && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                cache.put(cacheKey, result);
                db.getCache().writeItemCache(this, cache);
            }
        }
       
        return result.booleanValue();
    }

    /**
     * Retrieves a list of the names of all content items on this document
     * @throws WGAPIException
     */
    public List getItemNames() throws WGAPIException {
        return this.getCore().getItemNames();
    }

    /**
     * Removes an item from the document
     *
     * @param name
     *            Name of the item
     * @throws WGAPIException          
     */
    public void removeItem(String name) throws WGAPIException {
        this.getCore().removeItem(name);
        setEdited(true);
    }

    /**
     * Attaches a file to the document.
     * The file will be stored at the document with the same name that it has in file system.
     *
     * @param file
     *            The file on disk.
     * @throws WGAPIException
     */
    public boolean attachFile(File file) throws WGAPIException {
        boolean result = getCore().attachFile(file);
        if (result == true) {
            setEdited(true);
        }
       
        if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            String primary = getPrimaryFileName();
            if (primary == null) {
                setPrimaryFileName(file.getName());
            }
        }
       
       
        return result;
    }
   
    /**
     * Renames a file exception on this document
     * @param oldFileName The current file name
     * @param newFileName The new file name
     * @throws WGAPIException
     * @throws IOException
     */
    public void renameFile(String oldFileName, String newFileName) throws WGAPIException, IOException {
     
      if (!hasFile(oldFileName)) {
        throw new WGIllegalArgumentException("Unable to rename file '" + oldFileName + "'. There is no file with that name attached on this document.");
      }
     
        if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            String convertedName = getDatabase().convertFileNameForAttaching(oldFileName);
            String primary = getPrimaryFileName();
            if (convertedName.equals(primary)) {
                setPrimaryFileName(newFileName);
            }
        }
     
      if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA4_1) {
        // dispatch to core
        getCore().renameFile(oldFileName, newFileName);
        setEdited(true);
      }
      else {
        // backend does not support optimized file handling
        // detach and attach the file for renaming
        attachFile(getFileData(oldFileName), newFileName);
        removeFile(oldFileName);
      }
     
     
    }
   
    /**
     * Takes data from an input stream and attaches the data as file
     * under the given name.
     * This actually is a wrapper for {@link #attachFile(File)} that creates a temporary file of the desired name
     * and attaches this. This is becaues some WGAPI implementations cannot attach a data stream directly but need
     * a physical file.
     * @param stream The input stream providing the file data
     * @param filename The file name under which the file will be attached
     * @throws WGAPIException
     * @throws IOException
     */
    public boolean attachFile(InputStream stream, String filename) throws WGAPIException, IOException {
       
        TemporaryFile tempFile = new TemporaryFile(filename, stream, WGFactory.getTempDir());
        attachFile(tempFile.getFile());
        tempFile.deleteOnEviction(getDatabase().getSessionContext());
        setEdited(true);
        return true;
       
    }

    /**
     * Extracts a file attachment from this document
     * if the filename contains the path separator '/' it is replaced by '�'
     *
     * @param name
     *            The name of the file attachment
     * @param folder
     *            The folder to extract the file to     *          
     * @return The extracted File.
     * @throws IOException
     * @throws WGAPIException
     * @deprecated
     */
    public File extractFile(String name, File folder) throws IOException, WGAPIException {

        if (!folder.isDirectory()) {
            return null;
        }

        InputStream in = getFileData(name);
        if (in == null) {
            return null;
        }

        if (name.indexOf("/") != -1) {
            name = name.replace('/', '�');
        }
       
        File outFile = new File(folder, name);
        outFile.getParentFile().mkdirs();
        OutputStream out = new FileOutputStream(outFile);

        byte[] buf = new byte[2048];
        int len;
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
        return outFile;

    }

    /**
     * Removes a file attachment from the document
     * Note: When this method is called you should save the document before attempting to attach a
     * new file of the same name. Some WGAPI implementations will throw exceptions when you remove
     * and attach files of the same name without saving in between.
     *
     * @param name
     *            Name of the file attachment
     * @throws WGAPIException
     */
    public boolean removeFile(String name) throws WGAPIException {
       
        String convertedName = getDatabase().convertFileNameForAttaching(name);
        if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            String primary = getPrimaryFileName();
            if (convertedName.equals(primary)) {
                setPrimaryFileName(null);
            }
        }
       
        boolean result = getCore().removeFile(name);
        setEdited(true);
        return result;
    }

    /**
     * Removes the document from the database
     *
     * @throws WGAPIException
     */
    public abstract boolean remove() throws WGAPIException;
   
    protected synchronized boolean innerRemove() throws WGAPIException {
        performRemoveCheck();
        return db.remove(this, getDocumentKeyObj(), true);

    }

    /**
     * Performs a validation for removing the document at the current state, just like it actually was removed.
     * This method finishes quietly if the removal check succeeded. Otherwise an adequate
     * {@link WGAuthorisationException} is thrown.
     * For checking remove permissions without catching an exception see {@link #mayRemove()}.
     *
     * @throws WGAuthorisationException
     * @throws WGAPIException
     */
    public void performRemoveCheck() throws WGAuthorisationException, WGAPIException {
       
        if (this instanceof WGDesignDocument) {
            if (!db.getSessionContext().isDesigner()) {
                throw new WGAuthorisationException("You are not authorized to delete design documents in this database!");
            }
           
            if (!db.getSessionContext().getUserAccess().mayDeleteDocuments()) {
                throw new WGAuthorisationException("You are not authorized to delete documents in this database!");
            }
        }
       
        else {
            if (db.getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_EDITOR || !db.getSessionContext().getUserAccess().mayDeleteDocuments()) {
               
             // In project mode all authors are allowed to delete content documents, no matter the ACL
                if (!(getDatabase().isProjectMode() && this instanceof WGContent && db.getSessionContext().getAccessLevel() >= WGDatabase.ACCESSLEVEL_AUTHOR)) {
                    throw new WGAuthorisationException("You are not authorized to delete documents in this database!");
                }
            }
        }
       
    }

    /**
     * Creates a clone of this document in another database
     *
     * @param db
     *            The target database
     * @param ref
     *            A reference document in the target database. For struct
     *            entries, this is the parent entry in the target db. For
     *            contents this is the struct entry in the target db.
     * @return The clone
     * @throws WGAPIException
     */
    public abstract WGDocument createClone(WGDatabase db, WGDocument ref) throws WGAPIException;

    /**
     * Tests if caching for this document is enabled. If so, the document caches
     * item and metadata values.
     */
    public boolean isCachingEnabled() {
        return cachingEnabled && !temporary;
    }

    /**
     * Controls if item/meta caching is enabled for this document.
     *
     * @param b
     */
    public void setCachingEnabled(boolean b) {
        cachingEnabled = b;
    }

    /**
     * Tests if the document with it's current modifications has been saved. This call is passed to the backend document, unlike isEdited() which is maintained by the WGAPI.
     * @throws WGAPIException
     */
    protected boolean isSaved() throws WGAPIException {
        return getCore().isSaved();
    }

    /**
     * Gets the names list for past authors of this document. The indexing of
     * the given list corresponds to the indexes of the lists given by
     * getPastEditDates().
     * @throws WGAPIException
     */
    public List getPastAuthors() throws WGAPIException {
        return getMetaDataList(META_PASTAUTHORS);
    }

    /**
     * Gets the list of dates, when this document was edited in the past. The
     * indexing of the given list corresponds to the indexes of the lists given
     * by getPastAuthors().
     * @throws WGAPIException
     */
    public List getPastEditDates() throws WGAPIException {
        return getMetaDataList(META_PASTEDITDATES);
    }

    /**
     * Returns the revision number of this document.
     * @throws WGAPIException
     */
    public int getRevision() throws WGAPIException {
        return ((Integer)getMetaData(META_REVISION)).intValue();
    }

    /**
     * Returns all meta names that are supported by the current document type.
     * @throws WGSystemException
     */
    public List<String> getMetaNames() throws WGSystemException {
        Field[] fields = getClass().getFields();
        List<String> result = new ArrayList<String>();
        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            if (field.getName().indexOf("META_") == 0) {
                try {
                    String metaName = (String) field.get(this);
                    result.add(metaName);
                }
                catch (IllegalAccessException e) {
                    throw new WGSystemException("Unable to retrieve metanames for class '" + getClass().getName() + "'.", e);
                }
            }
        }
        return result;
    }

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

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

        return getCore().getNativeObject();
    }

    /**
     * String used in WGStructEntry fields for edit access to symbolize that noone may edit this document.
     */
    public static final String NOONE_ALLOWED = "%none";

    /**
     * Returns if the document was edited and the current state of the document is not yet saved.
     */
    public boolean isEdited() {
        return getDocumentSessionContext().isEdited();
    }

    /**
     * @param b
     */
    protected void setEdited(boolean b) {
        getDocumentSessionContext().setEdited(b);
    }
   
    /**
     * Manually sets this document as being edited.
     * This is useful for making the document show up in {@link WGSessionContext#getEditedDocuments()}.
     */
    public void markEdited() {
        setEdited(true);
    }

    /**
     * Pushed the data of this document to another document of the same type.
     * This method is allowed to save the target document if necessary, but is not required to.
     * @param doc The target document receiving the data.
     * @throws WGAPIException If something goes wrong
     */
    public void pushData(WGDocument doc) throws WGAPIException {
       
        // Push extension data fields
        if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5 && doc.getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            // Clear all extension data
            if (doc.getExtensionDataNames().size() > 0) {
                doc.removeAllExtensionData();
                doc.save();
            }
       
            // Copy extension data
            Iterator extDataNames = getExtensionDataNames().iterator();
            String extDataName;
            while (extDataNames.hasNext()) {
                extDataName = (String) extDataNames.next();
                try {
                    Object value = getExtensionData(extDataName);
                    if (value instanceof List) {
                        value = new ArrayList((List) value);
                    }
                    doc.writeExtensionData(extDataName, value);
                }
                catch (WGIllegalDataException e) {
                    WGFactory.getLogger().warn("Cannot copy item '" + extDataName + "' because of unrestorable data", e);
                }
            }
        }
       
    }

    /**
     * @param b
     */
    protected void setDeleted(boolean b) {
        _deleted = b;
       
    }

    private boolean dummy = false;

    private boolean disposed = false;

    /**
     * Returns, if this document is a "dummy content document", i.e. this document is nowhere stored in the database
     * but is only temporarily created to provide a content context for the current request.
     * @return boolean
     */
    public boolean isDummy() {
      return dummy;
    }

    /**
     * Sets if this document is a dummy content.
     * @param dummy The dummy to set
     */
    protected void setDummy(boolean dummy) {
      this.dummy = dummy;
    }

    /**
     * Returns the time when the document core was retrieved in the current session. Is null if there has no core been retrieved yet.
     */
    public Date getCoreRetrieved() {
        return new Date(getDocumentSessionContext().getCoreRetrievalTime());
    }

    /**
     * Returns the time that this document object was created
     */
    public Date getObjectCreated() {
        return objectCreated;
    }
   
    /**
     * Tests if a file of this name is attached to the document.
     * This method should also consider file name conversions that take place when attaching.
     * If this method returns true then attaching a file of this name will throw an error.
     * Vice versa if this method returns false it is safe to attach a file of this name,.
     * @param name The name of file to search
     * @return true, if a file of this name (maybe in a converted form) exists
     * @throws WGAPIException
     */
    public boolean hasFile(String name) throws WGAPIException {
       
        String convertedFileName = db.convertFileNameForAttaching(name);
        if (convertedFileName == null) {
            return false;
        }
       
        return getCore().hasFile(name);
       
    }
   
    /*
     *  (non-Javadoc)
     * @see de.innovationgate.webgate.api.locking.Lockable#lock(de.innovationgate.webgate.api.locking.LockOwner)
     */
    public void lock(LockOwner owner) throws WGAPIException {
        getDatabase().getLockManager().obtainLock(this, owner);
    }
   
    /**
     * locks the document for the current sessionContext 
     * @throws WGAPIException
     */
    public void lock() throws WGAPIException {       
        WGSessionContext sessionContext = getDatabase().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) {     
        getDatabase().getLockManager().releaseLock(this, owner);
    }
   
    /**
     * unlocks the document for the current sessioncontext    
     */
    public void unlock() {
       WGSessionContext sessionContext = getDatabase().getSessionContext();
       unlock(sessionContext);
    }   
   
    /*
     *  (non-Javadoc)
     * @see de.innovationgate.webgate.api.locking.Lockable#getLockStatus(de.innovationgate.webgate.api.locking.LockOwner)
     */
    public int getLockStatus(LockOwner owner) throws WGAPIException {
        return getDatabase().getLockManager().getLockStatus(this, owner);
    }
   
    /**
     * returns the lock status for the current sessioncontext
     * @throws WGAPIException
     */
    public int getLockStatus() throws WGAPIException {
        return getLockStatus(getDatabase().getSessionContext());
    }
   
    /*
     *  (non-Javadoc)
     * @see de.innovationgate.webgate.api.locking.Lockable#getParentLockable()
     */
    public abstract Lockable getParentLockable() throws WGAPIException;
   
    protected boolean isDisposed() {
        return disposed;
    }

    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + ((db == null) ? 0 : db.hashCode());
        result = PRIME * result + ((uniqueId == null) ? 0 : uniqueId.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 WGDocument other = (WGDocument) obj;
        if (db == null) {
            if (other.db != null)
                return false;
        }
        else if (!db.equals(other.db))
            return false;
        if (uniqueId == null) {
            if (other.uniqueId != null)
                return false;
        }
        else if (!uniqueId.equals(other.uniqueId))
            return false;
        return true;
    }

    protected String getOriginDatabase() {
        return originDatabase;
    }

    /**
     * Convenience method to remove all items from the document at once
     * @throws WGAPIException
     */
    public void removeAllItems() throws WGAPIException {
        Iterator names = getItemNames().iterator();
        while (names.hasNext()) {
            String name = (String) names.next();
            removeItem(name);
        }
    }
   
    /**
     * Convenience method to remove all extension data fields from the document at once
     * @throws WGAPIException
     */
    public void removeAllExtensionData() throws WGAPIException {
        Iterator names = getExtensionDataNames().iterator();
        while (names.hasNext()) {
            String name = (String) names.next();
            removeExtensionData(name);
        }
    }
   

   
    /**
     * Sets the given dates as created and modified dates of the document and saved it with them
     * @param created The created date
     * @param modified The modified date
     * @throws WGAPIException
     */
    public void saveWithGivenTimestamps(Date created, Date modified) throws WGAPIException {
       
        // We must ensure that the doc is saved before modifying created date
        // because implementations may rely on an unset created date
        // to tell if the document has been saved yet
        if (!isSaved()) {
            save();
        }
       
        setMetaData(WGDocument.META_CREATED, created);
        save(modified);
       
    }
   
    /**
     * Determines if this document serves metadata for file attachments
     * @return true, if file metadata is available
     * @throws WGAPIException
     */
    public boolean hasFileMetadata() throws WGAPIException {
        return getCore().hasFileMetadata();
    }

    public Object getExtensionData(String strName) throws WGAPIException {
       
        if (getDatabase().getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            return null;
        }
       
        return getCore().getExtensionData(strName);
       
    }

    public List getExtensionDataNames() throws WGAPIException {
        if (getDatabase().getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            return Collections.emptyList();
        }
       
        return getCore().getExtensionDataNames();
    }

    public void removeExtensionData(String strName) throws WGAPIException {
       
        if (getDatabase().getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException(CSFEATURE_EXTDATA, WGDatabase.CSVERSION_WGA5);
        }
       
        getCore().removeExtensionData(strName);
    }

    public void writeExtensionData(String strName, Object value) throws WGAPIException {
        if (getDatabase().getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException(CSFEATURE_EXTDATA, WGDatabase.CSVERSION_WGA5);
       
       
        getCore().writeExtensionData(strName, value);
    }
   
    /**
     * Returns the name of the primary attachment of this document
     * The primary attachment is a file attachment that is regarded the main attachment of this document (to be interpreted in a custom way).
     * By default the primary attachment of a documents is the first attachment that was attached to it.
     * Not all documents must feature a primary attachment. It can be manually set/changed via {@link #setPrimaryFileName(String)}.
     * @throws WGAPIException
     */
    public String getPrimaryFileName() throws WGAPIException {
       
        String name = (String) getExtensionData(EXT_PRIMARY_ATTACHMENT);
        if (name != null && hasFile(name)) {
            return name;
        }
        else {
            return null;
        }
       
    }
   
    /**
     * Manually sets the primary attachment of this document
     * @param name The name of the file to become primary attachment. Use null to remove the current primary attachment reference (not the attachment itself)
     * @throws WGAPIException
     */
    public void setPrimaryFileName(String name) throws WGAPIException {
       
        if (getDatabase().getContentStoreVersion() < WGDatabase.CSVERSION_WGA5) {
            throw new WGContentStoreVersionException(CSFEATURE_EXTDATA, WGDatabase.CSVERSION_WGA5);
        }
       
        if (name != null) {
            name = getDatabase().convertFileNameForAttaching(name);
            writeExtensionData(EXT_PRIMARY_ATTACHMENT, name);
        }
        else {
            removeExtensionData(EXT_PRIMARY_ATTACHMENT);
        }
    }
}
TOP

Related Classes of de.innovationgate.webgate.api.WGDocument

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.