Package de.innovationgate.webgate.api

Source Code of de.innovationgate.webgate.api.WGStructEntry$TestForContent

/*******************************************************************************
* 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.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import de.innovationgate.utils.UserHashMap;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGContent.SessionData;
import de.innovationgate.webgate.api.WGDocument.UniqueNameTester;
import de.innovationgate.webgate.api.WGSessionContext.DocumentContext;
import de.innovationgate.webgate.api.fake.WGFakeArea;
import de.innovationgate.webgate.api.fake.WGFakeContentType;
import de.innovationgate.webgate.api.locking.Lockable;
import de.innovationgate.webgate.api.utils.MasterSessionTask;

/**
* A struct entry that is used to build the site hierarchy, determine the
* content types at the hierarchy nodes and the editing rights in the hierarchy.
*/
public class WGStructEntry extends WGDocument implements Comparable, PageHierarchyNode {
   
    public class SessionData {
       
        private List backendReaders;
       
        public List getBackendReaders() {
            return backendReaders;
        }

        public void setBackendReaders(List backendReaders) {
            this.backendReaders = backendReaders;
        }

        private List backendPageEditors;

        public List getBackendPageEditors() {
            return backendPageEditors;
        }

        public void setBackendPageEditors(List backendContentEditors) {
            this.backendPageEditors = backendContentEditors;
        }
       
        private List backendChildEditors;

        public List getBackendChildEditors() {
            return backendChildEditors;
        }

        public void setBackendChildEditors(List backendChildEditors) {
            this.backendChildEditors = backendChildEditors;
        }

       

       
    }
 
    /**
     * The set of contents that belongs to one struct entry, divided up into different maps
     *
     */
    public class ContentSet {
       
        private Map<String,WGContent> allContent = new HashMap<String,WGContent>();
        private Map<String,WGContent> releasedContent = new HashMap<String,WGContent>();
        private Map<String,WGContent> archivedContent = new HashMap<String,WGContent>();
       
        public ContentSet() {
        }
       
        public ContentSet(List<WGContent> contents) throws WGAPIException {
           
            Iterator<WGContent> contentListIt = contents.iterator();
            WGContent newContent;
                              
            while (contentListIt.hasNext()) {
                newContent = (WGContent) contentListIt.next();
                String cachingKey = newContent.getLanguage().getName() + WGContentKey.TOKEN_DIVIDER + newContent.getVersion();

                if (newContent.getStatus().equals(WGContent.STATUS_ARCHIVE)) {
                    getArchivedContent().put(cachingKey, newContent);
                }
               
                else {
                    getAllContent().put(cachingKey, newContent);
                    if (newContent.getStatus().equals(WGContent.STATUS_RELEASE)) {
                        WGContent oldContent = (WGContent) getReleasedContent().put(newContent.getLanguage().getName(), newContent);
                        if (oldContent != null && oldContent.getVersion() != newContent.getVersion()) {
                            WGFactory.getLogger().warn("There are multiple released content versions for structentry " + newContent.getStructKey() + " (db:" + newContent.getDatabase().getDbReference() + ") on language " + newContent.getLanguage().getName() + ": Versions " + newContent.getVersion() + " and " + oldContent.getVersion());
                        }
                    }
                }
            }
           
        }
       
        /**
         * Returns all ACTIVE content for the struct entry (excluding archived ones), mapped by a string "language.version"
         */
        public Map<String,WGContent> getAllContent() {
            return allContent;
        }
        /**
         * Returns the archived content for the struct entry, mapped by a string "language.version"
         */
        public Map<String,WGContent> getArchivedContent() {
            return archivedContent;
        }
        /**
         * Returns the released content for the struct entry, mapped by the language code
         */
        public Map<String,WGContent> getReleasedContent() {
            return releasedContent;
        }
       
    }
   
    class TestForContent implements Runnable {

        private String _language;      
        private String _status;
        private boolean _contentExists;       
       
        public TestForContent(String language, String status) {
            _language = language;
            _status = status;
        }
       
        public TestForContent() {
            _language = null;
            _status = null;
        }
       
        public boolean doesContentExist() {

            Thread thread = new Thread(this);
            thread.start();

            try {
                thread.join();
            }
            catch (InterruptedException e) {

            }
           
            return _contentExists;

        }

        public void run() {

            try {
                getDatabase().openSession();
                WGContentList allContent = getAllContent(_status == null || _status.equals(WGContent.STATUS_ARCHIVE));   
                _contentExists = false;
                if( allContent != null && !allContent.isEmpty() ){                 
                  if( _language==null && _status==null){
                    _contentExists = true;
                  }
                  else{
                    Iterator itContent = allContent.iterator();                   
                    while( itContent.hasNext() ){ 
                      WGContent content = (WGContent)itContent.next();
                      if( _status == null ){
                        if( content.getLanguage().getName().equalsIgnoreCase(_language) ){
                          _contentExists = true;
                          break;
                        }
                      }
                      else{
                        if( _language == null ){
                          if( content.getStatus().equals(_status) ){
                            _contentExists = true;
                            break;
                          }
                        }
                        else{
                          if( content.getStatus().equals(_status) && content.getLanguage().getName().equalsIgnoreCase(_language) ){
                            _contentExists = true;
                            break;
                          }
                        }
                      }
                    }
                  }
                }
            } catch (Throwable t) {
                WGFactory.getLogger().error(
                        "Error testing for content in master session",
                        t);
            } finally {
                WGFactory.getInstance().closeSessions();
            }
        }
    }

    public static final String META_KEY = "KEY";
    public static final MetaInfo METAINFO_KEY = new MetaInfo(META_KEY, Object.class, null);

    public static final String META_TITLE = "TITLE";
   
    public static final String META_UNIQUENAME = "UNIQUENAME";
    public static final MetaInfo METAINFO_TITLE = new MetaInfo(META_TITLE, String.class, "");
    static {
      METAINFO_TITLE.setInputConverter(TITLE_CONVERTER);
      METAINFO_TITLE.setOutputConverter(TITLE_CONVERTER);
    }

    public static final String META_AREA = "AREA";
    public static final MetaInfo METAINFO_AREA = new MetaInfo(META_AREA, String.class, null);

    public static final String META_POSITION = "POSITION";
    public static final MetaInfo METAINFO_POSITION = new MetaInfo(META_POSITION, Number.class, new Integer(0));

    public static final String META_CONTENTTYPE = "CONTENTTYPE";
    public static final MetaInfo METAINFO_CONTENTTYPE = new MetaInfo(META_CONTENTTYPE, Object.class, null);
   
    public static final String META_READERS = "READERS";
    public static final MetaInfo METAINFO_READERS = new MetaInfo(META_READERS, String.class, Collections.EMPTY_LIST);
    static {
        METAINFO_READERS.setMultiple(true);
        METAINFO_READERS.setLuceneIndexType(MetaInfo.LUCENE_INDEXTYPE_FULLTEXT);
        METAINFO_READERS.setInputConverter(new MetaConverter() {

            public Object convert(WGDocument doc, MetaInfo metaInfo, Object value) throws WGAPIException {
                    List readers = (List) value;
                   
                    // Test if the current user is contained in the list. If not, he is added to avoid self disclosure
                    if (!WGDatabase.anyoneAllowed(readers) && !doc.getDatabase().isMemberOfUserList(readers)) {
                        readers.add(doc.getDatabase().getSessionContext().getUser());
                    }
                return readers;
            }
           
        });
    };
   
    public static final String META_PAGE_EDITORS = "PAGEEDITORS";
    public static final MetaInfo METAINFO_PAGE_EDITORS = new MetaInfo(META_PAGE_EDITORS, String.class, Collections.EMPTY_LIST);
    static { METAINFO_PAGE_EDITORS.setMultiple(true); };

    public static final String META_CHILD_EDITORS = "CHILDEDITORS";
    public static final MetaInfo METAINFO_CHILD_EDITORS = new MetaInfo(META_CHILD_EDITORS, String.class, Collections.EMPTY_LIST);
    static { METAINFO_CHILD_EDITORS.setMultiple(true); };
  
    public static final String META_WORKFLOW_NAME = "OVERRIDE_WORKFLOW";
    public static final MetaInfo METAINFO_WORKFLOW_NAME = new MetaInfo(META_WORKFLOW_NAME, String.class, null);
   
    public static final MetaInfo METAINFO_UNIQUENAME = new MetaInfo(META_UNIQUENAME, String.class, null);
    static  {
        METAINFO_UNIQUENAME.setMinCsVersion(WGDatabase.CSVERSION_WGA5);
        METAINFO_UNIQUENAME.setLowerCase(true);
        METAINFO_UNIQUENAME.addSynonym("PAGENAME");
        METAINFO_UNIQUENAME.addSynonym("PAGEUNIQUENAME");
        METAINFO_UNIQUENAME.addSynonym("PAGEDOCNAME");
    }
   
    public static final String META_PUBLISHED = "PUBLISHED";
    public static final MetaInfo METAINFO_PUBLISHED = new MetaInfo(META_PUBLISHED, Map.class, Collections.emptyMap());
    static {
        METAINFO_PUBLISHED.setMinCsVersion(WGDatabase.CSVERSION_WGA5);
    }




    /**
     * No content has yet been fetched for this struct entry.
     */
    public static final int FETCHSTATE_NONE = 0;

    /**
     * Only active contents (non-archived) have been fetched for this struct
     * entry.
     */
    public static final int FETCHSTATE_ACTIVE_ONLY = 1;

    /**
     * All contents - including archived - have been fetched for this struct
     * entry.
     */
    public static final int FETCHSTATE_ARCHIVED = 2;

    private Object retrievalKey = null;

    private WGDocumentKey area = null;

    protected WGDocumentKey parentEntry = null;

    private WGDocumentListCache childEntries = null;

    /*
    private Map fetchedContentForUsers = Collections
            .synchronizedMap(new HashMap());
    */
   
    //B00003852
    private UserHashMap contentCache = null;
    private Integer contentCount = null;
    private static final String FETCH_STATE_KEY = "FETCH_STATE";
    private static final Object FETCH_CONTENT_CACHE = "CONTENT_CACHE";
    private static final Object FETCH_RELEASED_CONTENT_CACHE = "RELEASED_CONTENT_CACHE_";
   
    /**
     * Determines if this entry is a root entry
     * @throws WGAPIException
     */
    public boolean isRoot() throws WGAPIException {
        if (this.getParentEntry() == null) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Sets allowed editors for child entries and their content (if they inherit
     * their editing rights)
     *
     * @param editors
     * @throws WGAPIException
     */
    public boolean setChildEditors(List editors) throws WGAPIException {
        return setMetaData(META_CHILD_EDITORS, editors);
    }
    /**
     * Sets names that are allowed to edit this struct and it's contents.
     *
     * @param editors
     * @throws WGAPIException
     */
    public void setPageEditors(List editors) throws WGAPIException {
        setMetaData(META_PAGE_EDITORS, editors);
    }
   
    /**
     * Old version of {@link #setPageEditors(List)}. Avoid in new developments.
     * @param editors
     * @throws WGAPIException
     * @deprecated
     */
    public boolean setStructEditors(List editors) throws WGAPIException {
        setPageEditors(editors);
        return true;
    }

    /**
     * Determines if this entry has child entries
     * @throws WGAPIException
     */
    public boolean hasChildren() throws WGAPIException {

        if (this.getChildEntries().size() > 0) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Returns a list of names that are allowed to edit this struct document and
     * it's contents.
     * @throws WGAPIException
     */
    public List getPageEditors() throws WGAPIException {
        return (List) this.getMetaData(WGStructEntry.META_PAGE_EDITORS);
    }
   
    /**
     * Old version for {@link #getPageEditors()}. Avoid in new developments.
     * @throws WGAPIException
     * @deprecated
     */
    public List getStructEditors() throws WGAPIException {
        return getPageEditors();
    }

    /**
     * Returns the editors allowed to edit child entries and content of this
     * struct entry
     * @throws WGAPIException
     */
    public List getChildEditors() throws WGAPIException {
        return (List) this.getMetaData(WGStructEntry.META_CHILD_EDITORS);
    }

    /**
     * Returns the parent entry, null if this is a root entry.
     * @throws WGAPIException
     */
    public WGStructEntry getParentEntry() throws WGAPIException {

        // Double checked cache (un-synchronized and synchronized)
        WGDocument theEntry = null;
        if (this.parentEntry != null && isCachingEnabled()) {
            theEntry = this.parentEntry.getDocument(getDatabase());
        }
       
        if (!isCachingEnabled() || theEntry == null) {
            WGOperationKey op = db.obtainOperationKey(
                    WGOperationKey.OP_STRUCT_PARENT, String
                            .valueOf(getStructKey()));
            synchronized (op) {

                try {
                    op.setUsed(true);
                    if (this.parentEntry != null && isCachingEnabled()) {
                        theEntry = this.parentEntry.getDocument(getDatabase());
                    }
                   
                    if (!isCachingEnabled() || theEntry == null) {
                        theEntry = this.db.getParentEntry(this);
                        if (theEntry == null) {
                            theEntry = this;
                        }
                        if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                            this.parentEntry = theEntry.getDocumentKeyObj();
                        }
                    }
                }
                finally {
                    op.setUsed(false);
                }
            }
        }

        if (!WGUtils.nullSafeEquals(theEntry, this)) {
            return (WGStructEntry) theEntry;
        } else {
            return null;
        }
    }

    /**
     * Returns the area of this struct entry
     * @throws WGAPIException
     */
    public WGArea getArea() throws WGAPIException {

        WGArea theArea = null;
        if (this.area != null) {
            theArea = (WGArea) this.area.getDocument(getDatabase());
        }
       
        if (!isCachingEnabled() || theArea == null) {
            if (this.isRoot()) {
                theArea = this.db.getArea((String) this.getMetaData(META_AREA));
            }
            else {
                theArea = this.getRootEntry().getArea();
            }
           
            if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                this.area = theArea.getDocumentKeyObj();
            }
        }

        if (theArea != null) {
            return theArea;
        } else {
            theArea = (WGArea) db
                    .getOrCreateDesignDocumentObject(new WGFakeArea(db, String
                            .valueOf(this.getMetaData(META_AREA))));
            theArea.setDummy(true);
            return theArea;
        }

    }

    /**
     * Constructor. Should not be used outside the WGAPI
     *
     * @param db
     * @param doc
     * @throws WGAPIException
     */
    public WGStructEntry(WGDatabase db, WGDocumentCore doc) throws WGAPIException {
        super(db, doc);
        gatherRetrievalKeys();

        // B00003852
        this.contentCache = db.getUserHashMapGroup().newUserHashMap(getDocumentKey() + "///fetchState");
       
        if (db.getUserCacheLatency() != 0) {
            this.contentCache.setMapLatency(db.getUserCacheLatency() * 1000 * 60);
        }
    }

    private void gatherRetrievalKeys() throws WGAPIException {
        this.retrievalKey = this.getMetaData(META_KEY);
    }



    /**
     * Gets the sibling entries (including the current struct entry)
     * @throws WGAPIException
     */
    public WGStructEntryList getSiblingEntries() throws WGAPIException {
        if (this.isRoot()) {
            return this.getArea().getRootEntries();
        } else {
            WGStructEntry entry = this.getParentEntry();
            if (entry != null) {
                return entry.getChildEntries();
            } else {
                WGFactory.getLogger().error(
                        "Could not get parent entry for struct '"
                                + getStructKey() + "'");
                return null;
            }
        }
    }

    /**
     * Returns the root entry of this entry
     * @throws WGAPIException
     */
    public WGStructEntry getRootEntry() throws WGAPIException {

        WGStructEntry entry = this;
        while (!entry.isRoot()) {
            entry = entry.getParentEntry();
        }
        return entry;

    }

    /**
     * Returns the position number.
     * @throws WGAPIException
     */
    public Number getPosition() throws WGAPIException {
        return (Number) this.getMetaData(META_POSITION);
    }
   
    /**
     * Returns a path of position numbers for all ancestors of the current struct entry
     * @return A list of position numbers for the struct entry and all it's ancestors, beginning with the current struct entry and proceeding up the hierarchy
     * @throws WGAPIException
     */
    public List getPositionPath() throws WGAPIException {
   
        List path = new ArrayList();
        WGStructEntry entry = this;
        while (entry != null) {
            path.add(entry.getPosition());
            entry = entry.getParentEntry();
        }

        Collections.reverse(path);
        return path;
       
    }

    /**
     * Sets the position number.
     *
     * @param pos
     * @throws WGAPIException
     */
    public boolean setPosition(int pos) throws WGAPIException {
        return setMetaData(META_POSITION, new Integer(pos));
    }

    /**
     * Returns the child entries of this struct entry
     * @throws WGAPIException
     */
    public WGStructEntryList getChildEntries() throws WGAPIException {

        List theEntries = getDatabase().fetchDocumentListCache(this.childEntries, WGDocument.TYPE_STRUCTENTRY);
        if (!isCachingEnabled() || theEntries == null) {
            WGOperationKey op = db.obtainOperationKey(
                    WGOperationKey.OP_STRUCT_CHILDREN, String
                            .valueOf(getStructKey()));
            synchronized (op) {
                try {
                    op.setUsed(true);
                    theEntries = getDatabase().fetchDocumentListCache(this.childEntries, WGDocument.TYPE_STRUCTENTRY);
                    if (!isCachingEnabled() || theEntries == null) {
                        theEntries = this.db.getChildEntries(this);
                        if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                            this.childEntries = WGDocumentListCache.buildFromDocuments(theEntries);
                        }
                    }
                }
                finally {
                    op.setUsed(false);
                }
            }
        }
        return WGStructEntryList.create(theEntries);
    }

    /**
     * Returns all content documents under this struct entry
     *
     * @param includeArchived
     *            Specify true to also include archived documents in result.
     * @throws WGAPIException
     */
    public WGContentList getAllContent(boolean includeArchived) throws WGAPIException {
        ContentSet contentSet = getContentSet(includeArchived);
       
        if (includeArchived) {
            List allContent = new ArrayList();
            allContent.addAll(contentSet.getAllContent().values());
            allContent.addAll(contentSet.getArchivedContent().values());
            return WGContentList.create(allContent);
        } else {
            return WGContentList.create(contentSet.getAllContent().values());
        }
    }
      
       
    /**
     * Returns all active (non-archived) content under this struct entry.
     * @throws WGAPIException
     */
    public WGContentList getAllContent() throws WGAPIException {
        return getAllContent(false);
    }

    private boolean alreadyFetched(boolean includeArchived) {

        int status = getContentFetchState();
        if (status == FETCHSTATE_NONE) {
            return false;
        }

        if (includeArchived && status == FETCHSTATE_ACTIVE_ONLY) {
            return false;
        }

        return true;

    }

    /**
     * Returns a specific content of the given language and content version
     *
     * @param language
     * @param version
     * @throws WGAPIException
     */
    public WGContent getContent(String language, int version) throws WGAPIException {
        ContentSet contentSet = getContentSet(true);
        String cachingKey = language + WGContentKey.TOKEN_DIVIDER + version;

        // Try non-archived
        WGContent content = (WGContent) contentSet.getAllContent().get(cachingKey);
        if (content != null)  {
            return content;
        }
       
        // Try archived
        return (WGContent) contentSet.getArchivedContent().get(cachingKey);
    }
   
    /**
     * Returns a content of the given language and status, if any exists.
     * If multiple contents exist with these parameters a random content of
     * that group is returned.
     *
     * @param language The language of the content
     * @param status The workflow status of the content
     * @return A content with that parameters. null if none exists.
     * @throws WGAPIException
     */
    public WGContent getContent(String language, String status) throws WGAPIException {
       
        WGStructEntry.ContentSet contentSet = getContentSet(status.equals(WGContent.STATUS_ARCHIVE));
       
        Iterator contents;
        if (status.equals(WGContent.STATUS_ARCHIVE)) {
            contents = contentSet.getArchivedContent().values().iterator();
        }
        else {
            contents = contentSet.getAllContent().values().iterator();
        }
       
        WGContent content;
        while (contents.hasNext()) {
            content = (WGContent) contents.next();
            if (content.getLanguage().getName().equals(language) && content.getStatus().equals(status)) {
                return content;
            }
        }
       
        return null;
       
       
    }

    /**
     * Returns the title of the struct entry. We prohibit this on struct entries whose contents may not be read by the current user.
     * @throws WGAPIException
     */
    public String getTitle() throws WGAPIException {
       
        if (!mayReadContent()) {
            return null;
        }
       
        return this.getMetaData(META_TITLE).toString();
    }

    /**
     * Sets the struct title.
     *
     * @param title
     * @throws WGAPIException
     */
    public boolean setTitle(String title) throws WGAPIException {
        return this.setMetaData(META_TITLE, title);
    }
   
    /**
     * Returns the workflow name of the struct entry.
     * @throws WGAPIException
     */
    public String getWorkflowName() throws WGAPIException {
      String wfName = "";
      WGStructEntry pEntry = this;
     
      while( pEntry != null ){
        wfName = (String)pEntry.getMetaData(META_WORKFLOW_NAME);
        if( wfName != null && !wfName.equals("") ){
          break;
          }
        pEntry = pEntry.getParentEntry();
      }
     
      if( wfName == null || wfName.equals("") ){
        wfName = this.getContentType().getWorkflow();
      }
     
        return wfName;
    }

    /**
     * Sets the workflow name.
     * @param wfname The workflow name to set
     * @throws WGAPIException
     */
    public boolean setWorkflowName(String wfname) throws WGAPIException {
        return this.setMetaData(META_WORKFLOW_NAME, wfname);
    }
   
    /**
     * Returns the content type for this struct entry
     * @throws WGAPIException
     */
    public WGContentType getContentType() throws WGAPIException {


        WGContentType theContentType = null;
        String doctypeName = (String) this.getMetaData(META_CONTENTTYPE);
        if (doctypeName != null) {
            theContentType = (WGContentType) this.db.getContentType(doctypeName);
        }


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

    }
   
    /**
     * Change the content type for this struct entry and all its contents.
     * This is only possible while no contents are in a approval workflow.
     * @param contentType The content type to set
     * @throws WGAPIException
     */
    public void changeContentType(WGContentType contentType) throws WGAPIException {
     
      // master task to check for unreleased content
      MasterSessionTask checkUnreleasedContent = new MasterSessionTask(getDatabase()) {
      protected void exec(WGDatabase db) throws Throwable {
          Iterator contents = getAllContent().iterator();
          while (contents.hasNext()) {
            WGContent content = (WGContent) contents.next();
            if (!content.getStatus().equals(WGContent.STATUS_RELEASE)) {
              throw new WGIllegalStateException("ContentType cannot be changed bc. there is unreleased content in draft or review state.");
            }       
          }
       
      }       
      };
     
      // check for unreleased content
      try {
      checkUnreleasedContent.runWithExceptions();
    } catch (WGAPIException e)  {
      throw e;
    } catch (Throwable e) {
      throw new WGAPIException(e.getMessage(), e);
    }
   
    // Check if we may modify this content type
    performSaveCheck();
     
    // check if contenttype can be used by current user at this position
    // null is also allowed if user is at least designer
    WGDocument parent = null;
    if (isRoot()) {
      parent = getArea();
    } else {
      parent = getParentEntry();
    }   
        if (contentType != null && !contentType.mayCreateChildEntry(parent)) {
            throw new WGAuthorisationException("User is not allowed to use this content type at this position");
        }

        if (getDatabase().hasFeature(WGDatabase.FEATURE_FULLCONTENTFEATURES) && contentType == null && getDatabase().getSessionContext().isDesigner() == false) {
            throw new WGAuthorisationException("You are not authorized to create a struct entry without content type");
        }
       
     
        if (contentType != null) {
            setMetaData(WGStructEntry.META_CONTENTTYPE, (getDatabase().hasFeature(WGDatabase.FEATURE_USE_OBJECTS_AS_REFERENCES) ? contentType.getCore() : (Object) contentType.getName()));
        }
       
        // save changes
        save();
       
        // Trigger content saved events for all contents so they are reindexed
        for (WGContent content : getAllContent(true)) {
            WGContentEvent event = new WGContentEvent(WGContentEvent.TYPE_HASBEENSAVED, content.getDocumentKey(), content.getStructEntry().getContentType().getName(), content.getDatabase());
            event.setContent(content);
            getDatabase().fireContentEvent(event);
        }
    }

    /**
     * Returns the inner layout for this struct entry. This may be determined by
     * it's content type or by internal struct fields.
     *
     * @param mediaKey
     *            The media key of the desired layout
     * @return The TML module for the layout, if it exists for this media key,
     *         null otherwise
     * @throws WGAPIException
     */
    public WGTMLModule getInnerLayout(String mediaKey) throws WGAPIException {

        WGContentType contentType = getContentType();
        if (contentType != null) {
            return contentType.getInnerLayout(mediaKey);
        }
        else {
            return null;
        }
    }

    /**
     * Returns the outer layout for this struct entry. This may be determined by
     * it's content type or by internal struct fields.
     *
     * @param mediaKey
     *            The media key of the desired layout
     * @return The TML module for the layout, if it exists for this media key,
     *         null otherwise
     * @throws WGAPIException
     */
    public WGTMLModule getOuterLayout(String mediaKey) throws WGAPIException {

        WGContentType contentType = getContentType();
        if (contentType != null) {
            return contentType.getOuterLayout(mediaKey);
        }
        else {
            return null;
        }
    }

    /**
     * Compares two struct entries. Position is taken into account for
     * higher/lower but does not result in equality when positions are equal.
     * @throws WGAPIException
     */
    public int compareTo(Object obj) throws ClassCastException {

        // Only compares with other objects of this type
        if (obj.getClass() != this.getClass()) {
            throw new ClassCastException(obj.getClass().getName()
                    + " can only be compared to itself");
        }

       
        try {
           
            // Look if one is deleted - Then only the hashCode can compare them
            WGStructEntry otherEntry = (WGStructEntry) obj;
            if (this.isDeleted() || otherEntry.isDeleted()) {
                return (this.hashCode() == this.hashCode() ? 0 : -1);
            }
              
           
            // Try to compare via position
            Double thisPos = new Double(this.getPosition().doubleValue());
            Double otherPos = new Double(otherEntry.getPosition().doubleValue());
            int posCompare = thisPos.compareTo(otherPos);
            if (posCompare != 0)
                return posCompare;
           
           
            // Try to compare via title
          String thisTitle = WGUtils.getValueOrDefault(this.getTitle(), "");
          String otherTitle = WGUtils.getValueOrDefault(otherEntry.getTitle(), "");
          int titleCompare = getDatabase().getDefaultLanguageCollator().compare(thisTitle, otherTitle);
          if (titleCompare != 0)
              return titleCompare;
           
            // If everything else fails, compare Struct key
            return this.getStructKey().toString().compareTo(
                    otherEntry.getStructKey().toString());
           
        } catch (WGAPIException e) {
            throw new RuntimeException("Unable to compare bc. of WGAPIException", e);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see de.innovationgate.webgate.api.WGDocument#dropCache()
     */
    public void dropCache() throws WGAPIException {
        super.dropCache();
        this.contentCount  = null;
        dropRelations();
    }

    protected void dropRelations() {
        this.area = null;
        this.parentEntry = null;
        this.childEntries = null;
      
        if (this.contentCache != null) {
            this.contentCache.clear();
        }
    }

    /**
     * Returns the child level of this entry. 1 is root.
     * @throws WGAPIException
     */
    public Integer getLevel() throws WGAPIException {

        WGStructEntry entry = this;
        int level = 0;
        while (entry != null) {
            level++;
            entry = entry.getParentEntry();        
        }
        return new Integer(level);
    }

    /**
     * Returns the struct key.
     */
    public Object getStructKey() {
        return this.retrievalKey;
    }

    /**
     * Returns the released content of the given language
     *
     * @param strLanguage
     * @throws WGAPIException
     */
    public WGContent getReleasedContent(String strLanguage) throws WGAPIException {
       
        // Test for read access
        if (!mayReadContent()) {
            return null;
        }
       
        if (strLanguage == null) {
            strLanguage = getDatabase().getDefaultLanguage();
        }
       
        // If the content set is already fetched we might just take the content from it
        if (getContentFetchState() >= FETCHSTATE_ACTIVE_ONLY) {
            ContentSet contentSet = getContentSet(false);
            return (WGContent) contentSet.getReleasedContent().get(strLanguage.toLowerCase());
        }
       
        // Else look in released docs cache. If not available there use db.getReleasedContent() to retrieve it
        else {
            try {
                String cacheKey = FETCH_RELEASED_CONTENT_CACHE + strLanguage;
                WGContent content = (WGContent) getDatabase().fetchDocumentFromCache(this.contentCache, cacheKey);
                if (content == null) {
                    WGOperationKey op = getDatabase().obtainOperationKey(WGOperationKey.OP_STRUCT_CONTENTS, String.valueOf(getStructKey()));
                    synchronized (op) {
                        try {
                            op.setUsed(true);
                            content = (WGContent) getDatabase().fetchDocumentFromCache(this.contentCache, cacheKey);
                            if (content == null) {
                                content = getDatabase().getReleasedContent(this, strLanguage);
                                if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                                    if (content != null) {
                                        contentCache.put(cacheKey, content.getDocumentKeyObj());
                                    }
                                    else {
                                        getDatabase().mapNonExistentDocIndicator(contentCache, cacheKey, WGDocument.TYPE_CONTENT);
                                    }
                                }
                            }
                        }
                        finally {
                            op.setUsed(false);
                        }
                    }
                }

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

    /**
     * Tests if this entry has an released content for the given language.
     * This method is capable of regarding contents that the current user is not allowed to see.
     * @param strLanguage The language to test
     * @return true, if there is a released content, false otherwise
     */
    public boolean hasReleasedContent(String strLanguage) {
        TestForContent test = new TestForContent(strLanguage, WGContent.STATUS_RELEASE);
        return test.doesContentExist();       
    }
   
    /**
     * Tests if struct entry has content in given language code / status, even if the user doesn't have the authorisation to read it.
     * If language code parameter is null, proves on any language code.
     * If status parameter is null, proves on any status.
     * @throws WGAPIException
     */
    public boolean hasContent(String strLanguage, String strStatus) throws WGAPIException {
       
        // If only pure existence of any content is tested
        // we redirect to the optimized WGDatabase.hasContent() method
        if (strLanguage == null && strStatus == null) {
            return (getDatabase().hasContents(this));
        }
       
        TestForContent test = new TestForContent(strLanguage, strStatus);
        return test.doesContentExist();       
    }

    /**
     * Returns the number of content documents in any status or language, that are located at this struct entry.
     * This includes content documents that are invisible to the current user because of reader settings, so this
     * method may be used to determine the existence of such documents while they cannot be retrieved directly.
     * @throws WGAPIException
     */
    public int getContentCount() throws WGAPIException {
       
        if (contentCount == null) {
           
            // Technique 1: Try native content count functionalty (in-session) from backend
            try {
                contentCount = new Integer(getDatabase().getCore().getContentCount(this));
                return contentCount.intValue();
            }
            catch (WGNotSupportedException e) {
            }
            catch (WGAPIException e) {
                WGFactory.getLogger().error("Exception retrieving content count from " + getDocumentKey(), e);
            }
           
           
            // Techique 2: Retrieve number of contents from master session
            MasterSessionTask task = new MasterSessionTask(getDatabase()) {
                protected void exec(WGDatabase db) throws Throwable {
                    setResult(new Integer(WGStructEntry.this.getAllContent(true).size()));
                }
            };
           
            if (task.run() == true) {
                contentCount = ((Integer) task.getResult());;
            }
            else {
                throw new WGAPIException("Exception retrieving content count", task.getThrowable());
            }
        }
       
        return contentCount.intValue();
       
    }

    /**
     * Returns all released content documents under this struct entry, keyed by
     * their language code.
     * @throws WGAPIException
     */
    public Map getAllReleasedContent() throws WGAPIException {
        return new HashMap(getContentSet(false).getReleasedContent());
    }

    /**
     * @throws WGAPIException
     * @see WGDocument#retrieveCore()
     */
    protected WGDocumentCore retrieveCore() throws WGAPIException {
        return this.db.getCore().getStructEntryByKey(this.retrievalKey);
    }

    /**
     * Returns the next sibling by siblings index.
     * @throws WGAPIException
     */
    public WGStructEntry getNextSibling() throws WGAPIException {

        WGStructEntryList siblings = this.getSiblingEntries();
        int idx = siblings.getIndexOfEntry(this);
        return siblings.getByIndex(++idx);

    }

    /**
     * Returns the previous sibling by siblings index
     * @throws WGAPIException
     */
    public WGStructEntry getPreviousSibling() throws WGAPIException {

        WGStructEntryList siblings = this.getSiblingEntries();
        int idx = siblings.getIndexOfEntry(this);
        return siblings.getByIndex(--idx);

    }


    /**
     * Tests if the current user may edit this struct entry and it's contents.
     *
     * @return null, if the user may edit. If not, the WGStructEntry or WGArea
     *         object ist returned that denies editing access
     * @throws WGAPIException
     */
    public WGDocument mayEditEntryAndContent() throws WGAPIException {

        // returns null if user may edit Entry
        // Otherwise returns WGStructEntry, which prohibits user from editing
        // this Entry.

        // Test for read access. Without read access no write access (not even for managers)
        WGDocument prohibitingDoc = getReadProtectionCause();
        if (prohibitingDoc != null) {
            return prohibitingDoc;
        }
       
        // test if user has Admin-Role, if so grant access in anyway.
        if (this.getDatabase().getSessionContext().getAccessLevel() == WGDatabase.ACCESSLEVEL_MANAGER) {
            return null;
        }
       

        // Retrieve the list of editors. If we have retrieved backend data we prefer that one
        // bc. the current data may have been modified
        List editors = getEffectivePageEditors();
       
        // Case 1: Anyone allowed
        if (WGDatabase.anyoneAllowed(editors, false)) {
            WGFactory.getLogger().debug(
                    "mayEditEntryAndContent - Case 1: Anyone allowed!");
            return null;
        }

        // Case 2: regular struct-document - no editor is assigned, so lookup
        // editors from parent-Struct
        if (editors.isEmpty()
                || (editors.size() == 1 && (editors.get(0) == null || editors
                        .get(0).toString().equals("")))) {
            WGFactory.getLogger().debug(
                    "mayEditEntryAndContent: Recursion to the root!");
            if (this.isRoot()) {
                editors = this.getArea().getEditors();
                // Is user stated as Editor in the Area-Doc?
                if (editors.size() >= 1
                        && !editors.get(0).toString().equals("")
                        && !this.getDatabase().isMemberOfUserList(editors)) {
                    WGFactory
                            .getLogger()
                            .debug(
                                    "WGArea: mayEditEntryAndContent - Case 4: Not member of user list!");
                    return this.getArea();
                } else {
                    WGFactory
                            .getLogger()
                            .debug(
                                    "mayEditEntryAndContent - Case 1: Area-Document reached, user access granted if nobody specified!");
                    return null;
                }

            } else {
                return this.getParentEntry().mayEditChildren();
            }
        }
        // Case 3: nobody is allowed to edit children of this struct.
        else if (editors.size() == 1
                && editors.get(0).toString()
                        .equals(WGStructEntry.NOONE_ALLOWED)) {
            WGFactory.getLogger().debug(
                    "mayEditEntryAndContent - Case 3: No user allowed!");
            return this;
        }
        // Case 4: current struct-document prohibits user to edit children, so
        // return current struct-doc
        else if (editors.size() >= 1
                && !this.getDatabase().isMemberOfUserList(editors)) {
            WGFactory
                    .getLogger()
                    .debug(
                            "mayEditEntryAndContent - Case 4: Not member of user list!");
            return this;
        }
        // Case 5: everything is cool, user may create children :-)
        else {
            WGFactory.getLogger().debug(
                    "mayEditEntryAndContent - Case 5: User access granted!");
            return null;
        }
    }
   
    /**
     * Tests if the current user may modify each document of the current pages subtree.
     * This is intended to be tested on operations that involve each document of a page subtree including the current one, like moving it.
     * The method exits normally if the user may do this operation or with an {@link WGAuthorisationException} if he does not, holding detail information about the cause.
     */
    public void performSubtreeModificationCheck() throws WGAPIException, WGAuthorisationException {
       
        for (WGStructEntry child : getChildEntries()) {
           
            WGDocument causeDoc = child.mayEditEntryAndContent();
            if (causeDoc != null) {
                if (causeDoc instanceof WGStructEntry) {
                    WGStructEntry causeStruct = (WGStructEntry) causeDoc;
                    throw new WGAuthorisationException("Page " + causeStruct.getDocumentKey() + " prevents you from modifying the given page subtree");
                }
                else if (causeDoc instanceof WGArea) {
                    WGArea causeArea = (WGArea) causeDoc;
                    throw new WGAuthorisationException("Area " + causeArea.getName() + " prevents you from modifying the given page subtree");
                }
                else  {
                    throw new WGAuthorisationException("Document " + causeDoc.getDocumentKey() + " prevents you from modifying the given page subtree");
                }
            }
           
            if (child.getContentCount() != child.getAllContent(true).size()) {
                throw new WGAuthorisationException("A read protected document, invisible to you, prevents you from modifying the given page subtree");
            }
           
            child.performSubtreeModificationCheck();
           
        }
       
    }

   
    /**
     * Tests if the current user may read contents on this page and below, and if so returns the document whose settings prohibit it.
     * @return null, if the user may read. If not, the WGStructEntry or WGArea
     *         object ist returned that denies reading access
     * @throws WGAPIException
     */
    public WGDocument getReadProtectionCause() throws WGAPIException {
       
        if (!getDatabase().isSessionOpen()) {
            throw new WGClosedSessionException();
        }
       
        // Are hierarchical reader fields enabled ?
        if (!getDatabase().isPageReadersEnabled()) {
            return null;
        }
       
        // No read protection on master session
        if (getDatabase().getSessionContext().isMasterSession()) {
            return null;
        }
       
        // Parent check
        if (isRoot()) {
            if (!getArea().mayReadContent()) {
                return getArea();
            }
        }
        else {
            WGDocument prohibitingDoc = getParentEntry().getReadProtectionCause();
            if (prohibitingDoc != null) {
                return prohibitingDoc;
            }
        }
           
        // Local check
        List readers = getEffectiveReaders();
        if (!WGDatabase.anyoneAllowed(readers, true) && !getDatabase().isMemberOfUserList(readers)) {
            return this;
        }
       
        return null;

       
    }
   
    /**
     * Tests if the current user may read contents on this page and below
     * @return true if the user may, false if not
     * @throws WGAPIException
     */
    public boolean mayReadContent() throws WGAPIException {
       
        WGDocument cause = getReadProtectionCause();
        return (cause == null);
       
    }
   
    /**
     * Tests if the current user may edit child entries and their contents.
     *
     * @return null, if the user may edit. If not, the WGStructEntry or WGArea
     *         object ist returned that denies editing access
     * @throws WGAPIException
     */
    public WGDocument mayEditChildren() throws WGAPIException {
        // returns null if user may edit Children
        // Otherwise returns WGStructEntry, which prohibits user from editing
        // this Entry.

        // test if user has Admin-Role, if so grant access in anyway.
        if (this.getDatabase().getSessionContext().getAccessLevel() == WGDatabase.ACCESSLEVEL_MANAGER) {
            return null;
        }
       
        // Test for read access. Without read access no write acces
        WGDocument prohibitingDoc = getReadProtectionCause();
        if (prohibitingDoc != null) {
            return prohibitingDoc;
        }

        // fetch information about Struct-Editors
        List editors = getEffectiveChildEditors();

        // Case 1: Anyone allowed
        if (WGDatabase.anyoneAllowed(editors, false)) {
            WGFactory.getLogger().debug(
                    "mayEditChildren - Case 1: Anyone allowed!");
            return null;
        }

        // Case 2: regular struct-document - no editor is assigned, so lookup
        // editors from parent-Struct
        if (editors.isEmpty()
                || (editors.size() == 1 && editors.get(0).toString().equals(""))) {
          
            WGFactory.getLogger().debug(
                    "mayEditChildren: Recursion to the root!");
           
            if (this.isRoot()) {
                if (!getArea().mayEditAreaChildren()) {
                   return getArea();
                }
                else {
                   return null;
                }

            }
            else {
                return this.getParentEntry().mayEditChildren();
            }
        }
       
        // Case 3: nobody is allowed to edit children of this struct.
        else if (editors.size() == 1
                && editors.get(0).toString()
                        .equals(WGStructEntry.NOONE_ALLOWED)) {
            WGFactory.getLogger().debug(
                    "mayEditChildren - Case 3: No user allowed!");
            return this;
        }
       
        // Case 4: current struct-document prohibits user to edit children, so
        // return current struct-doc
        else if (editors.size() >= 1
                && !this.getDatabase().isMemberOfUserList(editors)) {
            WGFactory.getLogger().debug(
                    "mayEditChildren - Case 4: Not member of user list!");
            return this;
        }
       
        // Case 5: everything is cool, user may create children :-)
        else {
            WGFactory.getLogger().debug(
                    "mayEditChildren - Case 5: User access granted!");
            return null;
        }
    }

    /**
     * Create a child struct entry
     *
     * @param contentType
     *            Content type for new entry
     * @param title
     *            Title of new entry
     * @return The newly created entry
     * @throws WGAPIException
     */
    public WGStructEntry createChildEntry(WGContentType contentType,
            String title) throws WGAPIException {
        return this.getDatabase().createStructEntry(this, contentType, title);
    }

    /**
     * Create a content document under this struct entry.
     *
     * @param language
     *            The language for the content
     * @param title
     *            The title of the content
     * @return The newly created content
     * @throws WGAPIException
     */
    public WGContent createContent(WGLanguage language, String title)
            throws WGAPIException {
        return this.getDatabase().createContent(this, language, title);
    }
   
    /**
     * Create a content document under this struct entry.
     *
     * @param language
     *            The language for the content
     * @param title
     *            The title of the content
     * @return The newly created content
     * @throws WGAPIException
     */
    public WGContent createContent(String language, String title)
            throws WGAPIException {
        return this.getDatabase().createContent(this, getDatabase().getLanguage(language), title);
    }
   
    /**
     * Creates a new content for this struct entry, using the default language of the database.
     * @param title The title of the new content
     * @return The newly created content
     * @throws WGAPIException
     */
    public WGContent createContent(String title) throws WGAPIException {
       
        WGLanguage lang = getDatabase().getLanguage(getDatabase().getDefaultLanguage());
        if (lang != null) {
            return createContent(lang, title);
        }
        else {
            throw new WGCreationException("No default language configured for this database");
        }
       
    }

    /*
     * (Kein Javadoc)
     *
     * @see de.innovationgate.webgate.api.WGDocument#createClone(de.innovationgate.webgate.api.WGDatabase)
     */
    public WGDocument createClone(WGDatabase db, WGDocument ref)
            throws WGAPIException {

        if (!(ref instanceof WGStructEntry || ref instanceof WGArea)) {
            return null;
        }

        WGContentType contentType = db.getContentType((String) getMetaData(META_CONTENTTYPE));
        if (contentType == null) {
            throw new WGCreationException("The content type '" + getMetaData(META_CONTENTTYPE) + "' is not available in target database");
        }
       
        WGStructEntry newEntry = db.createStructEntry(getStructKey(), ref, contentType,
                getTitle());
        pushData(newEntry);

        if (newEntry.save(getLastModified())) {
            return newEntry;
        } else {
            return null;
        }

    }

    /*
     * (non-Javadoc)
     *
     * @see de.innovationgate.webgate.api.WGDocument#pushData(de.innovationgate.webgate.api.WGDocument)
     */
    public void pushData(WGDocument doc) throws WGAPIException {
       
        super.pushData(doc);
       
        WGStructEntry entry = (WGStructEntry) doc;
        entry.setPosition(getPosition().intValue());
       
        entry.setMetaData(META_WORKFLOW_NAME, getMetaData(META_WORKFLOW_NAME));
        entry.setMetaData(META_CONTENTTYPE, getMetaData(META_CONTENTTYPE));
        entry.setReaders(new ArrayList(getReaders()));
        entry.setChildEditors(new ArrayList(getChildEditors()));
        entry.setPageEditors(new ArrayList(getPageEditors()));
        entry.setTitle(getTitle());
       
        if (getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5 && doc.getDatabase().getContentStoreVersion() >= WGDatabase.CSVERSION_WGA5) {
            entry.setUniqueName(getUniqueName());
            entry.setPublished(new HashMap(getPublished()));
        }
       
        boolean wasRepositioned = isDifferentParent(entry);

        // We must test the target parents for existence. They may not yet exist (see #00000640)
        if (wasRepositioned) {
            if (this.getParentEntry() == null) {
                WGArea targetArea = entry.getDatabase().getArea(getArea().getName());
                if (targetArea != null) {
                    entry.getDatabase().moveStructEntry(entry, targetArea);
                }
            }
           
            else {
                WGStructEntry targetEntry = entry.getDatabase().getStructEntryByKey(getParentEntry().getStructKey());
                if (targetEntry != null) {
                    entry.getDatabase().moveStructEntry(entry, targetEntry);
                }
            }

        }
    }

    /**
     * Tests if this struct entry has a different parent than the given clone.
     * Used in synchronisation to test if the struct was repositioned since last sync.
     * @param clone The struct entry clone
     * @return true if it was repositioned, false otherwise
     * @throws WGAPIException
     */
    public boolean isDifferentParent(WGStructEntry clone) throws WGAPIException {
        // Look for reposition - We compare document keys bc. the tested documents may be from different databases, yet still represent the same document
        boolean wasRepositioned = false;
        if (this.isRoot() && clone.isRoot()) {
            wasRepositioned = (this.getArea().getDocumentKey().equals(clone.getArea().getDocumentKey()) ? false
                    : true);
        } else if (!this.isRoot() && !clone.isRoot()) {
            wasRepositioned = (this.getParentEntry().getDocumentKey().equals(
                    clone.getParentEntry().getDocumentKey()) ? false : true);
        } else if (this.isRoot() != clone.isRoot()) {
            wasRepositioned = true;
        }
        return wasRepositioned;
    }


    protected boolean remove(boolean performRemoveCheck) throws WGAPIException {


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

        if (performRemoveCheck) {
            performRemoveCheck();
        }

        // Deleting child contents and struct entries
        if (!db.hasFeature(WGDatabase.FEATURE_AUTOCASCADES_DELETIONS)
                && db.getSessionContext().isCascadeDeletions()) {

            // Delete all content
            Iterator doomed = getAllContent(true).iterator();
            while (doomed.hasNext()) {
                WGContent doomedContent = (WGContent) doomed.next();
                if (!doomedContent.isDeleted()) {
                    if (doomedContent.remove() == false) {
                        throw new WGBackendException("Unable to delete content " + doomedContent.getContentKey().toString());
                    }
                }
            }

            // There are still contents on this struct entry (that the current user is not allowed to read) so we cancel
            // Should not happen any more since we test this previously
            /*
            if (db.hasContents(this)) {
                throw new WGAuthorisationException("Unable to delete struct entry " + getStructKey() + " because it contains contents that are not under your access");
            }
            */

            // Delete all children
            doomed = getChildEntries().iterator();
            while (doomed.hasNext()) {
                WGStructEntry doomedEntry = (WGStructEntry) doomed.next();
                if (!doomedEntry.isDeleted()) {
                    if (doomedEntry.remove(false) == false) {
                        throw new WGBackendException("Unable to delete struct entry " + doomedEntry.getStructKey());
                    }
                }

            }

        }

        return innerRemove();
    }
   
    public boolean remove() throws WGAPIException {
        return remove(true);
    }

    /* (non-Javadoc)
     * @see de.innovationgate.webgate.api.WGDocument#performRemovalCheck()
     */
    public void performRemoveCheck() throws WGAuthorisationException, WGAPIException {
       
        super.performRemoveCheck();
       
        // Test general right to delete struct entries       
        if (db.getSessionContext().getAccessLevel() < WGDatabase.ACCESSLEVEL_EDITOR) {
          throw new WGAuthorisationException("You are not authorized to delete entries in this database!");           
        }

        // Test rights to delete this entry and content
        // Test rights to edit this entry and content
        if (getContentType() != null && !getContentType().mayCreateContent()) {
            throw new WGAuthorisationException("You are not allowed to delete documents of content type '" + getContentType().getName() + "'");
        }
       
        WGDocument cause = mayEditEntryAndContent();
        if (cause != null) {
            if (cause instanceof WGStructEntry) {
                WGStructEntry entry = (WGStructEntry) cause;
                throw new WGAuthorisationException(
                        "You cannot delete struct entry " + getStructKey() + " because the settings of struct entry " + entry.getStructKey() + " deny this");
            } else {
                WGArea area = (WGArea) cause;
                throw new WGAuthorisationException(
                        "You cannot delete struct entry " + getStructKey() + " because the settings of area '" + area.getName() + " deny this");
            }
        }

        // Test rights to delete children, if there are any
        if (hasChildren()) {
            cause = mayEditChildren();
            if (cause != null) {
                if (cause instanceof WGStructEntry) {
                    WGStructEntry entry = (WGStructEntry) cause;
                    throw new WGAuthorisationException(
                            "You cannot delete the children of struct entry " + getStructKey() + " because the settings of child entry " + entry.getStructKey() + " deny this");
                } else {
                    WGArea area = (WGArea) cause;
                    throw new WGAuthorisationException(
                            "You cannot delete the children of struct entry " + getStructKey() + " children, because the settings of area '" + area.getName() + " denies this");
                }
            }
        }
       
        // Test if there are read protected contents on this struct entry that we cannot see
        if (getContentCount() != getAllContent(true).size()) {
            throw new WGAuthorisationException("You cannot delete struct entry " + getStructKey() + " because it contains read protected contents invisible to you");
        }
       
        // Perform removal check on all child contents
        Iterator contents = getAllContent(true).iterator();
        while (contents.hasNext()) {
            ((WGContent) contents.next()).performRemoveCheck();
        }
       
        // Perform removal check on all child entries
        Iterator entries = getChildEntries().iterator();
        while (entries.hasNext()) {
            ((WGStructEntry) entries.next()).performRemoveCheck();
        }       
       
    }

    /*
     * (Kein Javadoc)
     *
     * @see de.innovationgate.webgate.api.WGDocument#save()
     */
    public boolean save(Date lastModified) throws WGAPIException {

        boolean result = super.save(lastModified);
       
        if (result == true) {
            gatherRetrievalKeys();
        }
       
        return result;
    }

    public void performSaveCheck() throws WGAPIException, WGAuthorisationException {
       
        super.performSaveCheck();       
       
        // Test rights to edit this entry and content
        if (getContentType() != null && !getContentType().mayCreateContent()) {
            throw new WGAuthorisationException("You are not allowed to edit documents of content type '" + getContentType().getName() + "'");
        }
       
        WGDocument cause = mayEditEntryAndContent();
        if (cause != null) {
            if (cause instanceof WGStructEntry) {
                WGStructEntry entry = (WGStructEntry) cause;
                throw new WGAuthorisationException(
                        "You cannot edit this struct entry, because the settings of struct entry " + entry.getStructKey() + " deny this");
            } else {
                throw new WGAuthorisationException(
                        "You cannot edit struct entries in this area");
            }
        }
       
        // Check if Unique Name exists
        if (db.hasFeature(WGDatabase.FEATURE_FULLCONTENTFEATURES) &&
                getDatabase().getSessionContext().isTestUniqueNames()) {
            boolean isUniqueNameOK = false;

            // B00004D4E
            if (this.getUniqueName() != null) {
                UniqueNameTester tester = null;
                try {
                    tester = new UniqueNameTester(this.getUniqueName(), null, getStructKey());
                    Thread proveThread = new Thread(tester);
                    proveThread.start();
                    proveThread.join();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                isUniqueNameOK = tester.isUniqueNameOK;
            }
            else {
                isUniqueNameOK = true;
            }

            if (!isUniqueNameOK) {
                throw new WGDuplicateKeyException("Unique name '" + this.getUniqueName() + "' already exists.");
            }
        }
    }
   
    /**
     * Returns the fetch state for content of this structentry as constant
     * FETCHSTATE_... WGA clients may determine by this state how expensive a
     * desired fetch operation might be. E.g. retrieving content including
     * archived versions may be expensive if it has not been fetched yet
     * Otherwise, everything that is already fetched is cheap to retrieve.
     */
    public int getContentFetchState() {
        /*
        Integer fetchState = (Integer) this.fetchedContentForUsers.get(content
                .getCurrentUser());
        if (fetchState != null) {
            return fetchState.intValue();
        } else {
            return FETCHSTATE_NONE;
        }*/
       
        //B00003852
        Integer fetchState = (Integer) this.contentCache.get(FETCH_STATE_KEY);
        if (fetchState != null) {
            return fetchState.intValue();
        } else {
            return FETCHSTATE_NONE;
        }
    }

    /**
     * Returns a path consisting of the structkeys from root entry to this
     * entry, divided by slashes. This information can be used as an easy
     * reference for the position of this entry in the area's hierarchy
     * @throws WGAPIException
     */
    public String getStructKeyPath() throws WGAPIException {

        List path = new ArrayList();
        WGStructEntry entry = this;
        while (entry != null) {
            path.add(String.valueOf(entry.getStructKey()));
            entry = entry.getParentEntry();
        }

        Collections.reverse(path);
        return WGUtils.serializeCollection(path, "/");

    }

    /**
     * Returns a path consisting of the entry titles from root entry to this
     * entry, divided by slashes This information can be used as an easy
     * reference for the position of this entry in the area's hierarchy
     * @throws WGAPIException
     */
    public String getTitlePath() throws WGAPIException {

        List path = new ArrayList();
        WGStructEntry entry = this;
        while (entry != null) {
            path.add(String.valueOf(entry.getTitle()));
            entry = entry.getParentEntry();
        }

        Collections.reverse(path);
        return WGUtils.serializeCollection(path, "/");

    }

    /**
     * Determines if this struct entry is a descendant of the given entry, i.e.
     * is a part of it's child hierarchy.
     *
     * @param entry
     *            The entry that may be a descendant
     * @return yes if it is a descendant of the given entry, false if not
     * @throws WGAPIException
     */
    public boolean isDescendantOf(WGStructEntry entry) throws WGAPIException {

        WGStructEntry parent = getParentEntry();
        while (parent != null) {
            if (parent.equals(entry)) {
                return true;
            }
            parent = parent.getParentEntry();
        }
        return false;

    }
   
    protected boolean knowsParentEntry() {
        return (this.parentEntry != null);
    }
   
    /*
     *  (non-Javadoc)
     * @see de.innovationgate.webgate.api.locking.Lockable#getParentLockable()
     */
    public Lockable getParentLockable() throws WGAPIException {
        WGStructEntry parentEntry =  getParentEntry();
        if (parentEntry != null) {
            return parentEntry;
        }
        else {
            return getArea();
        }

    }
   
    /**
     * Returns the set of contents for the current WGStructEntry
     * @param includeArchived Set true to also retrieve archived contents
     * @return The content set
     * @throws WGAPIException
     */
    public ContentSet getContentSet(boolean includeArchived) throws WGAPIException {
       
        // Test for read access
        if (!mayReadContent()) {
            return new ContentSet();
        }
       
        // This set is returned. It is filled either from cache or from backend database
        ContentSet contentSet = null;
       
        // We retrieve the cached contents. We can bypass this potentially expensive operation if we know the needed set has not yet been fetched
        // or caching is disabled
        List<WGContent> cachedContents = null;
        if (alreadyFetched(includeArchived) && isCachingEnabled()) {
            cachedContents = getDatabase().fetchDocumentListCache((WGDocumentListCache) this.contentCache.get(FETCH_CONTENT_CACHE), WGDocument.TYPE_CONTENT);
        }
       
        // Double checked locking
        if (cachedContents == null) {
            int operationType = (includeArchived ? WGOperationKey.OP_STRUCT_CONTENTS_INCLUDING_ARCHIVED : WGOperationKey.OP_STRUCT_CONTENTS);
            WGOperationKey op = db.obtainOperationKey(operationType, String.valueOf(getStructKey()));
            synchronized (op) {
       
                try {
                    op.setUsed(true);
                    if (alreadyFetched(includeArchived) && isCachingEnabled()) {
                        cachedContents = getDatabase().fetchDocumentListCache((WGDocumentListCache) this.contentCache.get(FETCH_CONTENT_CACHE), WGDocument.TYPE_CONTENT);
                    }
                   
                    if (cachedContents == null) {
                       
                        WGContentList contentList = this.db.getAllContent(this, includeArchived);
                        contentSet = new ContentSet(contentList);
                       
                        // If cache writing enabled we will write the contentSet to the cache
                        if (isCachingEnabled() && getDatabase().getSessionContext().isCacheWritingEnabled()) {
                           
                            // B00003852
                            contentCache.clearUser();
                           
                            List<WGContent> contents = new ArrayList();
                            contents.addAll(contentSet.getAllContent().values());
                            if (includeArchived) {
                                contents.addAll(contentSet.getArchivedContent().values());
                            }
                            this.contentCache.put(FETCH_CONTENT_CACHE, WGDocumentListCache.buildFromDocuments(contents));
                            this.contentCache.put(FETCH_STATE_KEY, new Integer(includeArchived ? FETCHSTATE_ARCHIVED : FETCHSTATE_ACTIVE_ONLY));
                        }
                    }
                }
                finally {
                    op.setUsed(false);
                }
            }
        }
       
        // Fill buffers from cache
        // Might be ineffective to build the set on each request from a list. Should we cache a more organized form?
        if (contentSet == null && cachedContents != null) {
            contentSet = new ContentSet(cachedContents);
        }

       return contentSet;
       
       
    }

    /**
     * Returns the index of the current struct entry in the list of it's siblings
     * @throws WGAPIException
     */
    public int getSiblingIndex() throws WGAPIException {
        return getSiblingEntries().getIndexOfEntry(this);
    }

    public Class getChildNodeType() {
        return WGStructEntry.class;
    }

    public List getChildNodes() throws WGAPIException {
        return new ArrayList(getChildEntries());
    }
   
    public String getNodeKey() throws WGAPIException {
        return getDocumentKey();
    }

    public String getNodeTitle(String language) throws WGAPIException {
        WGContent con = getReleasedContent(language);
        if (con != null) {
            return con.getTitle();
        }
        else {
            return getTitle();
        }
    }

    public PageHierarchyNode getParentNode() throws WGAPIException {
        if (isRoot()) {
            return getArea();
        }
        else {
            return getParentEntry();
        }
    }
   
    /**
     * Returns the archived contents for one language, as list in the order of their version numbers.
     * @param language The language to retrieve archived contents for
     * @return List of archived contents of the given language, ordered by version ascending
     * @throws WGAPIException
     */
    public List<WGContent> getArchivedContent(String language) throws WGAPIException {
       
        ContentSet set = getContentSet(true);
        List<WGContent> archivedContent = new ArrayList<WGContent>();
        Iterator<WGContent> contents = set.getArchivedContent().values().iterator();
        WGContent content;
        while (contents.hasNext()) {
            content = (WGContent) contents.next();
            if (content.getLanguage().getName().equals(language)) {
                archivedContent.add(content);
            }
        }
       
        Collections.sort(archivedContent, new Comparator<WGContent>() {

            public int compare(WGContent o1, WGContent o2) {
                try {
                    int v1 = o1.getVersion();
                    int v2 = o2.getVersion();
                    return v2 - v1;
                }
                catch (WGAPIException e) {
                    throw new RuntimeException("WGAPI Exception comparing content versions", e);
                }
            }
           
        });
        return archivedContent;
       
    }

    protected void updateBackendCaches(WGDocumentCore core) {
        super.updateBackendCaches(core);
       
        try {
            de.innovationgate.webgate.api.WGStructEntry.SessionData sessionData = getSessionData();
            sessionData.setBackendReaders((List) core.getMetaData(WGStructEntry.META_READERS));
            sessionData.setBackendPageEditors((List) core.getMetaData(WGStructEntry.META_PAGE_EDITORS));
            sessionData.setBackendChildEditors((List) core.getMetaData(WGStructEntry.META_CHILD_EDITORS));
        }
        catch (WGAPIException e) {
            WGFactory.getLogger().error("Error updating struct entry backend cache", e);
        }
    }
   
    private SessionData getSessionData() {
       
        DocumentContext con = getDocumentSessionContext();
        SessionData data = (SessionData) con.getCustomData();
        if (data == null) {
            data = new SessionData();
            con.setCustomData(data);
        }
        return data;
       
    }
   
    /**
     * Returns the unique name of this struct entry, null if there is none
     * @throws WGAPIException
     */
    public String getUniqueName() throws WGAPIException {
        return (String) getMetaData(META_UNIQUENAME);
    }
   
    /**
     * Sets the unique name of this struct entry
     */
    public void setUniqueName(String name) throws WGAPIException {
        setMetaData(META_UNIQUENAME, name);
    }
   
    /**
     * Returns the dates when this page was first published in its available languages
     * The returned map is keyed by language codes. The corresponding value is the date/time when the page was first published in this language.
     * @throws WGAPIException
     */
    public Map<String,Date> getPublished() throws WGAPIException {
        return (Map<String,Date>) getMetaData(META_PUBLISHED);
    }
   
    protected void setPublished(Map<String,Date> published) throws WGAPIException {
        setMetaData(META_PUBLISHED, published);
    }
   
    /**
     * Implements the visitor pattern on the page hierarchy branch of this struct entry. The visitor visits this and all struct entries below and all of their (non-archived) contents.
     * @param visitor
     * @throws WGAPIException
     */
    public void visit(WGPageVisitor visitor) throws WGAPIException {
       
        visitor.visit(this);
       
        List<WGContent> contents = getAllContent();
        for (WGContent content : contents) {
            visitor.visit(content);
        }
       
        List<WGStructEntry> children = getChildEntries().asList();
        for (WGStructEntry child : children) {
            child.visit(visitor);
        }
       
    }
   
    /**
     * Checks if the current user may create a new child struct entry of the given content type. If so the method exits normally. Otherwise an rexception is thrown.
     * @param ct The content type to use.
     * @throws WGAPIException @throws WGAPIException If the user may not create the document. The exception informs about the reason.
     */
    public void performChildCreationCheck(WGContentType ct) throws WGAPIException {
        getDatabase().performStructCreationCheck(null, this, ct);
    }
   
    /**
     * Checks if the current user may create a new child page with the given data. If so the method exits normally. Otherwise an exception is thrown.
     * @param ct The content type to use for the struct entry
     * @param lang The language to use for the content
     * @throws WGAPIException If the user may not create the document. The exception informs about the reason.
     */
    public void performChildPageCreationCheck(WGContentType ct, WGLanguage lang) throws WGAPIException {
        performChildCreationCheck(ct);
        getDatabase().performContentCreationCheck(null, lang);
    }
   
    /**
     * Creates a new child page, including struct entry and content.
     * Both documents are already saved when the method exists.
     * @param contentType The content type of the page
     * @param title The title of the page that will be used for both struct entry
     * @param language The language of the content to create. Leave null to use databases default language.
     * @return The content of the created page
     * @throws WGAPIException
     */
    public WGContent createChildPage(WGContentType contentType, String title, String language) throws WGAPIException {
       
        if (language == null) {
            language = getDatabase().getDefaultLanguage();
        }
       
        WGLanguage lang = getDatabase().getLanguage(language);
        performChildPageCreationCheck(contentType, lang);
       
        WGStructEntry entry = createChildEntry(contentType, title);
        entry.save();
        WGContent content = entry.createContent(lang, title);
        content.save();
        return content;
       
    }

    @Override
    public int getType() {
        return WGDocument.TYPE_STRUCTENTRY;
    }
   
    /**
     * Returns a list of allowed readers of contents on this struct entry. This method only returns the readers set on this entry, not those that are effective in hierarchy.
     * This is only supported on content stores of version 5.
     * @return List
     * @throws WGAPIException
     */
    public List getReaders() throws WGAPIException {
        List readers = (List) this.getMetaData(WGArea.META_READERS);
        if (readers != null) {
            return readers;
        }
        else {
            return new ArrayList();
        }
    }
   
    /**
     * Sets the allowed readers for this struct entry. This is only supported on content stores of version 5.
     * @param list
     * @throws WGAPIException
     */
    public boolean setReaders(List list) throws WGAPIException {
        return setMetaData(META_READERS, list);
    }
   
    /**
     * Returns the list of currently effective readers. These may differ from the currently set readers in a yet unsaved document.
     * @throws WGAPIException
     */
    public List getEffectiveReaders() throws WGAPIException {
       
        List readers = getSessionData().getBackendReaders();
        if (readers == null) {
            readers = getReaders();
        }
        return readers;
       
    }
   
    /**
     * Returns the list of currently effective page editors.. These may differ from the currently set editors in a yet unsaved document.
     * @throws WGAPIException
     */
    public List getEffectivePageEditors() throws WGAPIException {
       
        List editors = getSessionData().getBackendPageEditors();
        if (editors == null) {
            editors = getPageEditors();
        }
        return editors;
       
    }
   
    /**
     * Returns the list of currently effective child editors.. These may differ from the currently set editors in a yet unsaved document.
     * @throws WGAPIException
     */
    public List getEffectiveChildEditors() throws WGAPIException {
       
        List editors = getSessionData().getBackendChildEditors();
        if (editors == null) {
            editors = getChildEditors();
        }
        return editors;
       
    }
   
}
TOP

Related Classes of de.innovationgate.webgate.api.WGStructEntry$TestForContent

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.