org.opencms.ade.contenteditor.CmsContentService.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.ade.contenteditor.CmsContentService.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.ade.contenteditor;

import org.opencms.acacia.shared.CmsAttributeConfiguration;
import org.opencms.acacia.shared.CmsEntity;
import org.opencms.acacia.shared.CmsEntityAttribute;
import org.opencms.acacia.shared.CmsEntityHtml;
import org.opencms.acacia.shared.CmsType;
import org.opencms.acacia.shared.CmsValidationResult;
import org.opencms.ade.containerpage.CmsContainerpageService;
import org.opencms.ade.containerpage.CmsElementUtil;
import org.opencms.ade.containerpage.shared.CmsCntPageData;
import org.opencms.ade.containerpage.shared.CmsContainer;
import org.opencms.ade.containerpage.shared.CmsContainerElement;
import org.opencms.ade.contenteditor.shared.CmsContentDefinition;
import org.opencms.ade.contenteditor.shared.CmsEditorConstants;
import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService;
import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.collectors.A_CmsResourceCollector;
import org.opencms.file.types.CmsResourceTypeXmlContent;
import org.opencms.flex.CmsFlexController;
import org.opencms.gwt.CmsGwtService;
import org.opencms.gwt.CmsRpcException;
import org.opencms.gwt.shared.CmsModelResourceInfo;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.CmsLocaleManager;
import org.opencms.json.JSONObject;
import org.opencms.jsp.CmsJspTagEdit;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.relations.CmsCategory;
import org.opencms.relations.CmsCategoryService;
import org.opencms.search.galleries.CmsGallerySearch;
import org.opencms.search.galleries.CmsGallerySearchResult;
import org.opencms.util.CmsStringUtil;
import org.opencms.util.CmsUUID;
import org.opencms.widgets.CmsCategoryWidget;
import org.opencms.widgets.I_CmsWidget;
import org.opencms.workplace.CmsDialog;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.workplace.editors.CmsEditor;
import org.opencms.workplace.editors.CmsXmlContentEditor;
import org.opencms.xml.CmsXmlContentDefinition;
import org.opencms.xml.CmsXmlEntityResolver;
import org.opencms.xml.CmsXmlException;
import org.opencms.xml.CmsXmlUtils;
import org.opencms.xml.I_CmsXmlDocument;
import org.opencms.xml.containerpage.CmsADESessionCache;
import org.opencms.xml.content.CmsXmlContent;
import org.opencms.xml.content.CmsXmlContentErrorHandler;
import org.opencms.xml.content.CmsXmlContentFactory;
import org.opencms.xml.content.I_CmsXmlContentEditorChangeHandler;
import org.opencms.xml.types.CmsXmlDynamicCategoryValue;
import org.opencms.xml.types.I_CmsXmlContentValue;
import org.opencms.xml.types.I_CmsXmlSchemaType;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;

import org.dom4j.Element;

import com.google.common.collect.Sets;

/**
 * Service to provide entity persistence within OpenCms. <p>
 */
public class CmsContentService extends CmsGwtService implements I_CmsContentService {

    /** The logger for this class. */
    protected static final Log LOG = CmsLog.getLog(CmsContentService.class);

    /** The type name prefix. */
    static final String TYPE_NAME_PREFIX = "http://opencms.org/types/";

    /** The serial version id. */
    private static final long serialVersionUID = 7873052619331296648L;

    /** The session cache. */
    private CmsADESessionCache m_sessionCache;

    /** The current users workplace locale. */
    private Locale m_workplaceLocale;

    /**
     * Returns the entity attribute name representing the given content value.<p>
     *
     * @param contentValue the content value
     *
     * @return the attribute name
     */
    public static String getAttributeName(I_CmsXmlContentValue contentValue) {

        return getTypeUri(contentValue.getContentDefinition()) + "/" + contentValue.getName();
    }

    /**
     * Returns the entity attribute name to use for this element.<p>
     *
     * @param elementName the element name
     * @param parentType the parent type
     *
     * @return the attribute name
     */
    public static String getAttributeName(String elementName, String parentType) {

        return parentType + "/" + elementName;
    }

    /**
     * Returns the entity id to the given content value.<p>
     *
     * @param contentValue the content value
     *
     * @return the entity id
     */
    public static String getEntityId(I_CmsXmlContentValue contentValue) {

        String result = CmsContentDefinition.uuidToEntityId(contentValue.getDocument().getFile().getStructureId(),
                contentValue.getLocale().toString());
        String valuePath = contentValue.getPath();
        if (valuePath.contains("/")) {
            result += "/" + valuePath.substring(0, valuePath.lastIndexOf("/"));
        }
        if (contentValue.isChoiceOption()) {
            result += "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + contentValue.getName() + "["
                    + contentValue.getXmlIndex() + "]";
        }
        return result;
    }

    /**
     * Returns the RDF annotations required for in line editing.<p>
     *
     * @param value the XML content value
     *
     * @return the RDFA
     */
    public static String getRdfaAttributes(I_CmsXmlContentValue value) {

        return "about=\"" + CmsContentService.getEntityId(value) + "\" property=\""
                + CmsContentService.getAttributeName(value) + "\"";
    }

    /**
     * Returns the RDF annotations required for in line editing.<p>
     *
     * @param parentValue the parent XML content value
     * @param childNames the child attribute names separated by '|'
     *
     * @return the RDFA
     */
    public static String getRdfaAttributes(I_CmsXmlContentValue parentValue, String childNames) {

        StringBuffer result = new StringBuffer();
        result.append("about=\"");
        result.append(CmsContentDefinition.uuidToEntityId(parentValue.getDocument().getFile().getStructureId(),
                parentValue.getLocale().toString()));
        result.append("/").append(parentValue.getPath());
        result.append("\" ");
        String[] children = childNames.split("\\|");
        result.append("property=\"");
        for (int i = 0; i < children.length; i++) {
            I_CmsXmlSchemaType schemaType = parentValue.getContentDefinition()
                    .getSchemaType(parentValue.getName() + "/" + children[i]);
            if (schemaType != null) {
                if (i > 0) {
                    result.append(" ");
                }
                result.append(getTypeUri(schemaType.getContentDefinition())).append("/").append(children[i]);
            }
        }
        result.append("\"");
        return result.toString();
    }

    /**
     * Returns the RDF annotations required for in line editing.<p>
     *
     * @param document the parent XML document
     * @param contentLocale the content locale
     * @param elementPath the element xpath to get the RDF annotation for
     *
     * @return the RDFA
     */
    public static String getRdfaAttributes(I_CmsXmlDocument document, Locale contentLocale, String elementPath) {

        I_CmsXmlSchemaType schemaType = document.getContentDefinition().getSchemaType(elementPath);
        StringBuffer result = new StringBuffer();
        if (schemaType != null) {
            result.append("about=\"");
            result.append(CmsContentDefinition.uuidToEntityId(document.getFile().getStructureId(),
                    contentLocale.toString()));
            result.append("\" property=\"");
            result.append(getTypeUri(schemaType.getContentDefinition())).append("/").append(elementPath);
            result.append("\"");
        }
        return result.toString();
    }

    /**
     * Returns the type URI.<p>
     *
     * @param xmlContentDefinition the type content definition
     *
     * @return the type URI
     */
    public static String getTypeUri(CmsXmlContentDefinition xmlContentDefinition) {

        return xmlContentDefinition.getSchemaLocation() + "/" + xmlContentDefinition.getTypeName();
    }

    /**
     * Fetches the initial content definition.<p>
     *
     * @param request the current request
     *
     * @return the initial content definition
     *
     * @throws CmsRpcException if something goes wrong
     */
    public static CmsContentDefinition prefetch(HttpServletRequest request) throws CmsRpcException {

        CmsContentService srv = new CmsContentService();
        srv.setCms(CmsFlexController.getCmsObject(request));
        srv.setRequest(request);
        CmsContentDefinition result = null;
        try {
            result = srv.prefetch();
        } finally {
            srv.clearThreadStorage();
        }
        return result;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#callEditorChangeHandlers(java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Collection)
     */
    public CmsContentDefinition callEditorChangeHandlers(String entityId, CmsEntity editedLocaleEntity,
            Collection<String> skipPaths, Collection<String> changedScopes) throws CmsRpcException {

        CmsContentDefinition result = null;
        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(editedLocaleEntity.getId());
        if (structureId != null) {
            CmsObject cms = getCmsObject();
            CmsResource resource = null;
            Locale locale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
            try {
                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
                ensureLock(resource);
                CmsFile file = cms.readFile(resource);
                CmsXmlContent content = getContentDocument(file, true).clone();
                checkAutoCorrection(cms, content);
                synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
                for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler()
                        .getEditorChangeHandlers()) {
                    Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition());
                    if (!Collections.disjoint(changedScopes, handlerScopes)) {
                        handler.handleChange(cms, content, locale, changedScopes);
                    }
                }
                result = readContentDefinition(file, content, entityId, locale, false, null, editedLocaleEntity);
            } catch (Exception e) {
                error(e);
            }
        }
        return result;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#cancelEdit(org.opencms.util.CmsUUID, boolean)
     */
    public void cancelEdit(CmsUUID structureId, boolean delete) throws CmsRpcException {

        try {
            getSessionCache().uncacheXmlContent(structureId);
            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
            if (delete) {
                ensureLock(resource);
                getCmsObject().deleteResource(getCmsObject().getSitePath(resource),
                        CmsResource.DELETE_PRESERVE_SIBLINGS);
            }
            tryUnlock(resource);
        } catch (Throwable t) {
            error(t);
        }
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#copyLocale(java.util.Collection, org.opencms.acacia.shared.CmsEntity)
     */
    public void copyLocale(Collection<String> locales, CmsEntity sourceLocale) throws CmsRpcException {

        try {
            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(sourceLocale.getId());

            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
            CmsFile file = getCmsObject().readFile(resource);
            CmsXmlContent content = getSessionCache().getCacheXmlContent(structureId);
            synchronizeLocaleIndependentForEntity(file, content, Collections.<String>emptyList(), sourceLocale);
            Locale sourceContentLocale = CmsLocaleManager
                    .getLocale(CmsContentDefinition.getLocaleFromId(sourceLocale.getId()));
            for (String loc : locales) {
                Locale targetLocale = CmsLocaleManager.getLocale(loc);
                if (content.hasLocale(targetLocale)) {
                    content.removeLocale(targetLocale);
                }
                content.copyLocale(sourceContentLocale, targetLocale);
            }
        } catch (Throwable t) {
            error(t);
        }
    }

    /**
     * @see org.opencms.gwt.CmsGwtService#getCmsObject()
     */
    @Override
    public CmsObject getCmsObject() {

        CmsObject result = super.getCmsObject();
        // disable link invalidation in the editor
        result.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE);
        return result;
    }

    /**
     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#loadContentDefinition(java.lang.String)
     */
    public CmsContentDefinition loadContentDefinition(String entityId) throws CmsRpcException {

        throw new CmsRpcException(new UnsupportedOperationException());
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadDefinition(java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection)
     */
    public CmsContentDefinition loadDefinition(String entityId, CmsEntity editedLocaleEntity,
            Collection<String> skipPaths) throws CmsRpcException {

        CmsContentDefinition definition = null;
        try {
            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
            CmsFile file = getCmsObject().readFile(resource);
            CmsXmlContent content = getContentDocument(file, true);
            if (editedLocaleEntity != null) {
                synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
            }
            definition = readContentDefinition(file, content,
                    CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), contentLocale,
                    false, null, editedLocaleEntity);
        } catch (Exception e) {
            error(e);
        }
        return definition;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadInitialDefinition(java.lang.String, java.lang.String, org.opencms.util.CmsUUID, java.lang.String, java.lang.String, java.lang.String, java.lang.String)
     */
    public CmsContentDefinition loadInitialDefinition(String entityId, String newLink, CmsUUID modelFileId,
            String editContext, String mainLocale, String mode, String postCreateHandler) throws CmsRpcException {

        CmsContentDefinition result = null;
        getCmsObject().getRequestContext().setAttribute(CmsXmlContentEditor.ATTRIBUTE_EDITCONTEXT, editContext);
        try {
            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
            getSessionCache().clearDynamicValues();
            getSessionCache().uncacheXmlContent(structureId);
            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) {
                result = readContentDefnitionForNew(newLink, resource, modelFileId, contentLocale, mode,
                        postCreateHandler);
            } else {
                CmsFile file = getCmsObject().readFile(resource);
                CmsXmlContent content = getContentDocument(file, false);
                result = readContentDefinition(file, content,
                        CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), contentLocale,
                        false, mainLocale != null ? CmsLocaleManager.getLocale(mainLocale) : null, null);
            }
        } catch (Throwable t) {
            error(t);
        }
        return result;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadNewDefinition(java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection)
     */
    public CmsContentDefinition loadNewDefinition(String entityId, CmsEntity editedLocaleEntity,
            Collection<String> skipPaths) throws CmsRpcException {

        CmsContentDefinition definition = null;
        try {
            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
            CmsFile file = getCmsObject().readFile(resource);
            CmsXmlContent content = getContentDocument(file, true);
            synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
            definition = readContentDefinition(file, content,
                    CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()), contentLocale, true,
                    null, editedLocaleEntity);
        } catch (Exception e) {
            error(e);
        }
        return definition;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#prefetch()
     */
    public CmsContentDefinition prefetch() throws CmsRpcException {

        String paramResource = getRequest().getParameter(CmsDialog.PARAM_RESOURCE);
        String paramDirectEdit = getRequest().getParameter(CmsEditor.PARAM_DIRECTEDIT);
        boolean isDirectEdit = false;
        if (paramDirectEdit != null) {
            isDirectEdit = Boolean.parseBoolean(paramDirectEdit);
        }
        String paramNewLink = getRequest().getParameter(CmsXmlContentEditor.PARAM_NEWLINK);
        boolean createNew = false;
        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramNewLink)) {
            createNew = true;
            paramNewLink = decodeNewLink(paramNewLink);
        }
        String paramLocale = getRequest().getParameter(CmsEditor.PARAM_ELEMENTLANGUAGE);
        Locale locale = null;
        CmsObject cms = getCmsObject();
        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramResource)) {
            try {
                CmsResource resource = cms.readResource(paramResource, CmsResourceFilter.IGNORE_EXPIRATION);
                if (CmsResourceTypeXmlContent.isXmlContent(resource)) {
                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramLocale)) {
                        locale = CmsLocaleManager.getLocale(paramLocale);
                    }
                    CmsContentDefinition result;
                    getSessionCache().clearDynamicValues();
                    if (createNew) {
                        if (locale == null) {
                            locale = OpenCms.getLocaleManager().getDefaultLocale(cms, paramResource);
                        }
                        CmsUUID modelFileId = null;
                        String paramModelFile = getRequest().getParameter(CmsWorkplace.PARAM_MODELFILE);

                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramModelFile)) {
                            modelFileId = cms.readResource(paramModelFile).getStructureId();
                        }

                        String mode = getRequest().getParameter(CmsEditorConstants.PARAM_MODE);
                        String postCreateHandler = getRequest()
                                .getParameter(CmsEditorConstants.PARAM_POST_CREATE_HANDLER);
                        result = readContentDefnitionForNew(paramNewLink, resource, modelFileId, locale, mode,
                                postCreateHandler);
                    } else {

                        CmsFile file = cms.readFile(resource);
                        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
                        getSessionCache().setCacheXmlContent(resource.getStructureId(), content);
                        if (locale == null) {
                            locale = OpenCms.getLocaleManager().getBestAvailableLocaleForXmlContent(getCmsObject(),
                                    resource, content);
                        }
                        result = readContentDefinition(file, content, null, locale, false, null, null);
                    }
                    result.setDirectEdit(isDirectEdit);
                    return result;
                }
            } catch (Throwable e) {
                error(e);
            }
        }
        return null;
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveAndDeleteEntities(org.opencms.acacia.shared.CmsEntity, java.util.List, java.util.Collection, java.lang.String, boolean)
     */
    public CmsValidationResult saveAndDeleteEntities(CmsEntity lastEditedEntity, List<String> deletedEntities,
            Collection<String> skipPaths, String lastEditedLocale, boolean clearOnSuccess) throws CmsRpcException {

        CmsUUID structureId = null;
        if (lastEditedEntity != null) {
            structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId());
        }
        if ((structureId == null) && !deletedEntities.isEmpty()) {
            structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0));
        }
        if (structureId != null) {
            CmsObject cms = getCmsObject();
            CmsResource resource = null;
            try {
                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
                ensureLock(resource);
                CmsFile file = cms.readFile(resource);
                CmsXmlContent content = getContentDocument(file, true);
                checkAutoCorrection(cms, content);
                if (lastEditedEntity != null) {
                    synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
                }
                for (String deleteId : deletedEntities) {
                    Locale contentLocale = CmsLocaleManager
                            .getLocale(CmsContentDefinition.getLocaleFromId(deleteId));
                    if (content.hasLocale(contentLocale)) {
                        content.removeLocale(contentLocale);
                    }
                }
                CmsValidationResult validationResult = validateContent(cms, structureId, content);
                if (validationResult.hasErrors()) {
                    return validationResult;
                }
                writeContent(cms, file, content, getFileEncoding(cms, file));

                writeCategories(file, content, lastEditedEntity);

                // update offline indices
                OpenCms.getSearchManager().updateOfflineIndexes();
                if (clearOnSuccess) {
                    tryUnlock(resource);
                    getSessionCache().uncacheXmlContent(structureId);
                }
            } catch (Exception e) {
                if (resource != null) {
                    tryUnlock(resource);
                    getSessionCache().uncacheXmlContent(structureId);
                }
                error(e);
            }
        }
        return null;
    }

    /**
     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntities(java.util.List)
     */
    public CmsValidationResult saveEntities(List<CmsEntity> entities) {

        throw new UnsupportedOperationException();
    }

    /**
     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntity(org.opencms.acacia.shared.CmsEntity)
     */
    public CmsValidationResult saveEntity(CmsEntity entity) {

        throw new UnsupportedOperationException();
    }

    /**
     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveValue(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
     */
    public String saveValue(String contentId, String contentPath, String localeString, String newValue)
            throws CmsRpcException {

        OpenCms.getLocaleManager();
        Locale locale = CmsLocaleManager.getLocale(localeString);

        try {
            CmsObject cms = getCmsObject();
            CmsResource element = cms.readResource(new CmsUUID(contentId), CmsResourceFilter.IGNORE_EXPIRATION);
            ensureLock(element);
            CmsFile elementFile = cms.readFile(element);
            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, elementFile);
            I_CmsXmlContentValue value = content.getValue(contentPath, locale);
            value.setStringValue(cms, newValue);
            for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler()
                    .getEditorChangeHandlers()) {
                Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition());
                if (handlerScopes.contains(contentPath)) {
                    handler.handleChange(cms, content, locale, Collections.singletonList(contentPath));
                }
            }
            content.synchronizeLocaleIndependentValues(cms, Collections.<String>emptyList(), locale);
            byte[] newData = content.marshal();
            elementFile.setContents(newData);
            cms.writeFile(elementFile);
            tryUnlock(elementFile);
            return "";
        } catch (Exception e) {
            error(e);
            return null;
        }

    }

    /**
     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#updateEntityHtml(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.lang.String)
     */
    public CmsEntityHtml updateEntityHtml(CmsEntity entity, String contextUri, String htmlContextInfo)
            throws Exception {

        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entity.getId());
        if (structureId != null) {
            CmsObject cms = getCmsObject();
            try {
                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
                CmsFile file = cms.readFile(resource);
                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
                String entityId = entity.getId();
                Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
                if (content.hasLocale(contentLocale)) {
                    content.removeLocale(contentLocale);
                }
                content.addLocale(cms, contentLocale);
                addEntityAttributes(cms, content, "", entity, contentLocale);
                CmsValidationResult validationResult = validateContent(cms, structureId, content);
                String htmlContent = null;
                if (!validationResult.hasErrors()) {
                    file.setContents(content.marshal());

                    JSONObject contextInfo = new JSONObject(htmlContextInfo);
                    String containerName = contextInfo.getString(CmsCntPageData.JSONKEY_NAME);
                    String containerType = contextInfo.getString(CmsCntPageData.JSONKEY_TYPE);
                    int containerWidth = contextInfo.getInt(CmsCntPageData.JSONKEY_WIDTH);
                    int maxElements = contextInfo.getInt(CmsCntPageData.JSONKEY_MAXELEMENTS);
                    boolean detailView = contextInfo.getBoolean(CmsCntPageData.JSONKEY_DETAILVIEW);
                    CmsContainer container = new CmsContainer(containerName, containerType, null, containerWidth,
                            maxElements, detailView, true, Collections.<CmsContainerElement>emptyList(), null,
                            null);
                    CmsUUID detailContentId = null;
                    if (contextInfo.has(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)) {
                        detailContentId = new CmsUUID(
                                contextInfo.getString(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID));
                    }
                    CmsElementUtil elementUtil = new CmsElementUtil(cms, contextUri, detailContentId,
                            getThreadLocalRequest(), getThreadLocalResponse(), contentLocale);
                    htmlContent = elementUtil.getContentByContainer(file,
                            contextInfo.getString(CmsCntPageData.JSONKEY_ELEMENT_ID), container, true);
                }
                return new CmsEntityHtml(htmlContent, validationResult);

            } catch (Exception e) {
                error(e);
            }
        }
        return null;
    }

    /**
     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#validateEntities(java.util.List)
     */
    public CmsValidationResult validateEntities(List<CmsEntity> changedEntities) throws CmsRpcException {

        CmsUUID structureId = null;
        if (changedEntities.isEmpty()) {
            return new CmsValidationResult(null, null);
        }
        structureId = CmsContentDefinition.entityIdToUuid(changedEntities.get(0).getId());
        if (structureId != null) {
            CmsObject cms = getCmsObject();
            Set<String> setFieldNames = Sets.newHashSet();
            try {
                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
                CmsFile file = cms.readFile(resource);
                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
                for (CmsEntity entity : changedEntities) {
                    String entityId = entity.getId();
                    Locale contentLocale = CmsLocaleManager
                            .getLocale(CmsContentDefinition.getLocaleFromId(entityId));
                    if (content.hasLocale(contentLocale)) {
                        content.removeLocale(contentLocale);
                    }
                    content.addLocale(cms, contentLocale);
                    setFieldNames.addAll(addEntityAttributes(cms, content, "", entity, contentLocale));
                }
                return validateContent(cms, structureId, content, setFieldNames);
            } catch (Exception e) {
                error(e);
            }
        }
        return new CmsValidationResult(null, null);
    }

    /**
     * Decodes the newlink request parameter if possible.<p>
     *
     * @param newLink the parameter to decode
     *
     * @return the decoded value
     */
    protected String decodeNewLink(String newLink) {

        String result = newLink;
        if (result == null) {
            return null;
        }
        try {
            result = CmsEncoder.decode(result);
            try {
                result = CmsEncoder.decode(result);
            } catch (Throwable e) {
                LOG.info(e.getLocalizedMessage(), e);
            }
        } catch (Throwable e) {
            LOG.info(e.getLocalizedMessage(), e);
        }

        return result;
    }

    /**
     * Returns the element name to the given element.<p>
     *
     * @param attributeName the attribute name
     *
     * @return the element name
     */
    protected String getElementName(String attributeName) {

        if (attributeName.contains("/")) {
            return attributeName.substring(attributeName.lastIndexOf("/") + 1);
        }
        return attributeName;
    }

    /**
     * Helper method to determine the encoding of the given file in the VFS,
     * which must be set using the "content-encoding" property.<p>
     *
     * @param cms the CmsObject
     * @param file the file which is to be checked
     * @return the encoding for the file
     */
    protected String getFileEncoding(CmsObject cms, CmsResource file) {

        String result;
        try {
            result = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true)
                    .getValue(OpenCms.getSystemInfo().getDefaultEncoding());
        } catch (CmsException e) {
            result = OpenCms.getSystemInfo().getDefaultEncoding();
        }
        return CmsEncoder.lookupEncoding(result, OpenCms.getSystemInfo().getDefaultEncoding());
    }

    /**
     * Parses the element into an entity.<p>
     *
     * @param content the entity content
     * @param element the current element
     * @param locale the content locale
     * @param entityId the entity id
     * @param parentPath the parent path
     * @param typeName the entity type name
     * @param visitor the content type visitor
     * @param includeInvisible include invisible attributes
     * @param editedLocalEntity the edited locale entity
     *
     * @return the entity
     */
    protected CmsEntity readEntity(CmsXmlContent content, Element element, Locale locale, String entityId,
            String parentPath, String typeName, CmsContentTypeVisitor visitor, boolean includeInvisible,
            CmsEntity editedLocalEntity) {

        String newEntityId = entityId
                + (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath) ? "/" + parentPath : "");
        CmsEntity newEntity = new CmsEntity(newEntityId, typeName);
        CmsEntity result = newEntity;

        List<Element> elements = element.elements();
        CmsType type = visitor.getTypes().get(typeName);
        boolean isChoice = type.isChoice();
        String choiceTypeName = null;
        // just needed for choice attributes
        Map<String, Integer> attributeCounter = null;
        if (isChoice) {
            choiceTypeName = type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME);
            type = visitor.getTypes().get(type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME));
            attributeCounter = new HashMap<String, Integer>();
        }
        int counter = 0;
        CmsObject cms = getCmsObject();
        String previousName = null;
        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath)) {
            parentPath += "/";
        }
        for (Element child : elements) {
            String attributeName = getAttributeName(child.getName(), typeName);
            String subTypeName = type.getAttributeTypeName(attributeName);
            if (visitor.getTypes().get(subTypeName) == null) {
                // in case there is no type configured for this element, the schema may have changed, skip the element
                continue;
            }
            if (!includeInvisible && !visitor.getAttributeConfigurations().get(attributeName).isVisible()) {
                // skip attributes marked as invisible, there content should not be transfered to the client
                continue;
            }
            if (isChoice && (attributeCounter != null)) {
                if (!attributeName.equals(previousName)) {
                    if (attributeCounter.get(attributeName) != null) {
                        counter = attributeCounter.get(attributeName).intValue();
                    } else {
                        counter = 0;
                    }
                    previousName = attributeName;
                }
                attributeCounter.put(attributeName, Integer.valueOf(counter + 1));
            } else if (!attributeName.equals(previousName)) {

                // reset the attribute counter for every attribute name
                counter = 0;

                previousName = attributeName;
            }
            if (isChoice) {
                result = new CmsEntity(newEntityId + "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + child.getName()
                        + "[" + counter + "]", choiceTypeName);
                newEntity.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, result);
            }
            String path = parentPath + child.getName();
            if (visitor.isDynamicallyLoaded(attributeName)) {
                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
                String attributeValue = getDynamicAttributeValue(content.getFile(), value, attributeName,
                        editedLocalEntity);
                result.addAttributeValue(attributeName, attributeValue);
            } else if (visitor.getTypes().get(subTypeName).isSimpleType()) {
                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
                result.addAttributeValue(attributeName, value.getStringValue(cms));
            } else {
                CmsEntity subEntity = readEntity(content, child, locale, entityId, path + "[" + (counter + 1) + "]",
                        subTypeName, visitor, includeInvisible, editedLocalEntity);
                result.addAttributeValue(attributeName, subEntity);

            }
            counter++;
        }
        return newEntity;
    }

    /**
     * Reads the types from the given content definition and adds the to the map of already registered
     * types if necessary.<p>
     *
     * @param xmlContentDefinition the XML content definition
     * @param locale the messages locale
     *
     * @return the types of the given content definition
     */
    protected Map<String, CmsType> readTypes(CmsXmlContentDefinition xmlContentDefinition, Locale locale) {

        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), null, locale);
        visitor.visitTypes(xmlContentDefinition, locale);
        return visitor.getTypes();
    }

    /**
     * Synchronizes the locale independent fields.<p>
     *
     * @param file the content file
     * @param content the XML content
     * @param skipPaths the paths to skip during locale synchronization
     * @param entities the edited entities
     * @param lastEdited the last edited locale
     *
     * @throws CmsXmlException if something goes wrong
     */
    protected void synchronizeLocaleIndependentFields(CmsFile file, CmsXmlContent content,
            Collection<String> skipPaths, Collection<CmsEntity> entities, Locale lastEdited)
            throws CmsXmlException {

        CmsEntity lastEditedEntity = null;
        for (CmsEntity entity : entities) {
            if (lastEdited
                    .equals(CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entity.getId())))) {
                lastEditedEntity = entity;
            } else {
                synchronizeLocaleIndependentForEntity(file, content, skipPaths, entity);
            }
        }
        if (lastEditedEntity != null) {
            // prepare the last edited last, to sync locale independent fields
            synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
        }
    }

    /**
     * Transfers values marked as invisible from the original entity to the target entity.<p>
     *
     * @param original the original entity
     * @param target the target entiy
     * @param visitor the type visitor holding the content type configuration
     */
    protected void transferInvisibleValues(CmsEntity original, CmsEntity target, CmsContentTypeVisitor visitor) {

        List<String> invisibleAttributes = new ArrayList<String>();
        for (Entry<String, CmsAttributeConfiguration> configEntry : visitor.getAttributeConfigurations()
                .entrySet()) {
            if (!configEntry.getValue().isVisible()) {
                invisibleAttributes.add(configEntry.getKey());
            }
        }
        CmsContentDefinition.transferValues(original, target, invisibleAttributes, visitor.getTypes(),
                visitor.getAttributeConfigurations(), true);
    }

    /**
     * Adds the attribute values of the entity to the given XML content.<p>
     *
     * @param cms the current cms context
     * @param content the XML content
     * @param parentPath the parent path
     * @param entity the entity
     * @param contentLocale the content locale
     *
     * @return the set of xpaths of simple fields in the XML content which were set by this method
     */
    private Set<String> addEntityAttributes(CmsObject cms, CmsXmlContent content, String parentPath,
            CmsEntity entity, Locale contentLocale) {

        Set<String> fieldsSet = Sets.newHashSet();
        addEntityAttributes(cms, content, parentPath, entity, contentLocale, fieldsSet);
        return fieldsSet;
    }

    /**
     * Adds the attribute values of the entity to the given XML content.<p>
     *
     * @param cms the current cms context
     * @param content the XML content
     * @param parentPath the parent path
     * @param entity the entity
     * @param contentLocale the content locale
     * @param fieldsSet set to store which fields were set in the XML content
     */
    private void addEntityAttributes(CmsObject cms, CmsXmlContent content, String parentPath, CmsEntity entity,
            Locale contentLocale, Set<String> fieldsSet) {

        for (CmsEntityAttribute attribute : entity.getAttributes()) {
            if (CmsType.CHOICE_ATTRIBUTE_NAME.equals(attribute.getAttributeName())) {
                List<CmsEntity> choiceEntities = attribute.getComplexValues();
                for (int i = 0; i < choiceEntities.size(); i++) {
                    List<CmsEntityAttribute> choiceAttributes = choiceEntities.get(i).getAttributes();
                    // each choice entity may only have a single attribute with a single value
                    assert (choiceAttributes.size() == 1) && choiceAttributes.get(0)
                            .isSingleValue() : "each choice entity may only have a single attribute with a single value";
                    CmsEntityAttribute choiceAttribute = choiceAttributes.get(0);
                    String elementPath = parentPath + getElementName(choiceAttribute.getAttributeName());
                    if (choiceAttribute.isSimpleValue()) {
                        String value = choiceAttribute.getSimpleValue();
                        I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
                        if (field == null) {
                            field = content.addValue(cms, elementPath, contentLocale, i);
                        }
                        field.setStringValue(cms, value);
                        fieldsSet.add(field.getPath());
                    } else {
                        CmsEntity child = choiceAttribute.getComplexValue();
                        I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
                        if (field == null) {
                            field = content.addValue(cms, elementPath, contentLocale, i);
                        }
                        addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
                    }
                }
            } else {
                String elementPath = parentPath + getElementName(attribute.getAttributeName());
                if (attribute.isSimpleValue()) {
                    List<String> values = attribute.getSimpleValues();
                    for (int i = 0; i < values.size(); i++) {
                        String value = values.get(i);
                        I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
                        if (field == null) {
                            field = content.addValue(cms, elementPath, contentLocale, i);
                        }
                        field.setStringValue(cms, value);
                        fieldsSet.add(field.getPath());
                    }
                } else {
                    List<CmsEntity> entities = attribute.getComplexValues();
                    for (int i = 0; i < entities.size(); i++) {
                        CmsEntity child = entities.get(i);
                        I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
                        if (field == null) {
                            field = content.addValue(cms, elementPath, contentLocale, i);
                        }
                        addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
                    }
                }
            }
        }
    }

    /**
     * Check if automatic content correction is required. Returns <code>true</code> if the content was changed.<p>
     *
     * @param cms the cms context
     * @param content the content to check
     *
     * @return <code>true</code> if the content was changed
     * @throws CmsXmlException if the automatic content correction failed
     */
    private boolean checkAutoCorrection(CmsObject cms, CmsXmlContent content) throws CmsXmlException {

        boolean performedAutoCorrection = false;
        try {
            content.validateXmlStructure(new CmsXmlEntityResolver(cms));
        } catch (CmsXmlException eXml) {
            // validation failed
            content.setAutoCorrectionEnabled(true);
            content.correctXmlStructure(cms);
            performedAutoCorrection = true;
        }
        return performedAutoCorrection;
    }

    /**
     * Evaluates any wildcards in the given scope and returns all allowed permutations of it.<p>
     *
     * a path like Paragraph* /Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image
     * in case max occurrence for Paragraph is 3
     *
     * @param scope the scope
     * @param definition the content definition
     *
     * @return the evaluate scope permutations
     */
    private Set<String> evaluateScope(String scope, CmsXmlContentDefinition definition) {

        Set<String> evaluatedScopes = new HashSet<String>();
        if (scope.contains("*")) {
            // evaluate wildcards to get all allowed permutations of the scope
            // a path like Paragraph*/Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image
            // in case max occurrence for Paragraph is 3

            String[] pathElements = scope.split("/");
            String parentPath = "";

            for (int i = 0; i < pathElements.length; i++) {
                String elementName = pathElements[i];
                boolean hasWildCard = elementName.endsWith("*");

                if (hasWildCard) {
                    elementName = elementName.substring(0, elementName.length() - 1);
                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
                    I_CmsXmlSchemaType type = definition.getSchemaType(parentPath);
                    Set<String> tempScopes = new HashSet<String>();
                    if (type.getMaxOccurs() == Integer.MAX_VALUE) {
                        throw new IllegalStateException(
                                "Can not use fields with unbounded maxOccurs in scopes for editor change handler.");
                    }
                    for (int j = 0; j < type.getMaxOccurs(); j++) {
                        if (evaluatedScopes.isEmpty()) {
                            tempScopes.add(elementName + "[" + (j + 1) + "]");
                        } else {

                            for (String evScope : evaluatedScopes) {
                                tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName + "[" + (j + 1) + "]"));
                            }
                        }
                    }
                    evaluatedScopes = tempScopes;
                } else {
                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
                    Set<String> tempScopes = new HashSet<String>();
                    if (evaluatedScopes.isEmpty()) {
                        tempScopes.add(elementName);
                    } else {
                        for (String evScope : evaluatedScopes) {
                            tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName));
                        }
                    }
                    evaluatedScopes = tempScopes;
                }
            }
        } else {
            evaluatedScopes.add(scope);
        }
        return evaluatedScopes;
    }

    /**
     * Evaluates the values of the locale independent fields and the paths to skip during locale synchronization.<p>
     *
     * @param content the XML content
     * @param syncValues the map of synchronization values
     * @param skipPaths the list o paths to skip
     */
    private void evaluateSyncLocaleValues(CmsXmlContent content, Map<String, String> syncValues,
            Collection<String> skipPaths) {

        CmsObject cms = getCmsObject();
        for (Locale locale : content.getLocales()) {
            for (String elementPath : content.getContentDefinition().getContentHandler().getSynchronizations()) {
                for (I_CmsXmlContentValue contentValue : content.getSimpleValuesBelowPath(elementPath, locale)) {
                    String valuePath = contentValue.getPath();
                    boolean skip = false;
                    for (String skipPath : skipPaths) {
                        if (valuePath.startsWith(skipPath)) {
                            skip = true;
                            break;
                        }
                    }
                    if (!skip) {
                        String value = contentValue.getStringValue(cms);
                        if (syncValues.containsKey(valuePath)) {
                            if (!syncValues.get(valuePath).equals(value)) {
                                // in case the current value does not match the previously stored value,
                                // remove it and add the parent path to the skipPaths list
                                syncValues.remove(valuePath);
                                int pathLevelDiff = (CmsResource.getPathLevel(valuePath)
                                        - CmsResource.getPathLevel(elementPath)) + 1;
                                for (int i = 0; i < pathLevelDiff; i++) {
                                    valuePath = CmsXmlUtils.removeLastXpathElement(valuePath);
                                }
                                skipPaths.add(valuePath);
                            }
                        } else {
                            syncValues.put(valuePath, value);
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns the change handler scopes.<p>
     *
     * @param definition the content definition
     *
     * @return the scopes
     */
    private Set<String> getChangeHandlerScopes(CmsXmlContentDefinition definition) {

        List<I_CmsXmlContentEditorChangeHandler> changeHandlers = definition.getContentHandler()
                .getEditorChangeHandlers();
        Set<String> scopes = new HashSet<String>();
        for (I_CmsXmlContentEditorChangeHandler handler : changeHandlers) {
            String scope = handler.getScope();
            scopes.addAll(evaluateScope(scope, definition));
        }
        return scopes;
    }

    /**
     * Returns the XML content document.<p>
     *
     * @param file the resource file
     * @param fromCache <code>true</code> to use the cached document
     *
     * @return the content document
     *
     * @throws CmsXmlException if reading the XML fails
     */
    private CmsXmlContent getContentDocument(CmsFile file, boolean fromCache) throws CmsXmlException {

        CmsXmlContent content = null;
        if (fromCache) {
            content = getSessionCache().getCacheXmlContent(file.getStructureId());
        }
        if (content == null) {
            content = CmsXmlContentFactory.unmarshal(getCmsObject(), file);
            getSessionCache().setCacheXmlContent(file.getStructureId(), content);
        }
        return content;
    }

    /**
     * Returns the value that has to be set for the dynamic attribute.
     *
     * @param file the file where the current content is stored
     * @param value the content value that is represented by the attribute
     * @param attributeName the attribute's name
     * @param editedLocalEntity the entities that where edited last
     * @return the value that has to be set for the dynamic attribute.
     */
    private String getDynamicAttributeValue(CmsFile file, I_CmsXmlContentValue value, String attributeName,
            CmsEntity editedLocalEntity) {

        if ((null != editedLocalEntity) && (editedLocalEntity.getAttribute(attributeName) != null)) {
            getSessionCache().setDynamicValue(attributeName,
                    editedLocalEntity.getAttribute(attributeName).getSimpleValue());
        }
        String currentValue = getSessionCache().getDynamicValue(attributeName);
        if (null != currentValue) {
            return currentValue;
        }
        if (null != file) {
            if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
                List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
                try {
                    categories = CmsCategoryService.getInstance().readResourceCategories(getCmsObject(), file);
                } catch (CmsException e) {
                    LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
                }
                I_CmsWidget widget = null;
                try {
                    widget = value.getContentDefinition().getContentHandler().getWidget(value);
                } catch (CmsXmlException e) {
                    LOG.error(e.getLocalizedMessage(), e);
                }
                if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
                    String mainCategoryPath = ((CmsCategoryWidget) widget).getStartingCategory(getCmsObject(),
                            getCmsObject().getSitePath(file));
                    StringBuffer pathes = new StringBuffer();
                    for (CmsCategory category : categories) {
                        if (category.getPath().startsWith(mainCategoryPath)) {
                            pathes.append(category.getBasePath()).append(category.getPath()).append(',');
                        }
                    }
                    String dynamicConfigString = pathes.length() > 0 ? pathes.substring(0, pathes.length() - 1)
                            : "";
                    getSessionCache().setDynamicValue(attributeName, dynamicConfigString);
                    return dynamicConfigString;
                }
            }
        }
        return "";

    }

    /**
     * Returns the path elements for the given content value.<p>
     *
     * @param content the XML content
     * @param value the content value
     *
     * @return the path elements
     */
    private String[] getPathElements(CmsXmlContent content, I_CmsXmlContentValue value) {

        List<String> pathElements = new ArrayList<String>();
        String[] paths = value.getPath().split("/");
        String path = "";
        for (int i = 0; i < paths.length; i++) {
            path += paths[i];
            I_CmsXmlContentValue ancestor = content.getValue(path, value.getLocale());
            int valueIndex = ancestor.getXmlIndex();
            if (ancestor.isChoiceOption()) {
                Element parent = ancestor.getElement().getParent();
                valueIndex = parent.indexOf(ancestor.getElement());
            }
            String pathElement = getAttributeName(ancestor.getName(), getTypeUri(ancestor.getContentDefinition()));
            pathElements.add(pathElement + "[" + valueIndex + "]");
            path += "/";
        }
        return pathElements.toArray(new String[pathElements.size()]);
    }

    /**
     * Returns the session cache.<p>
     *
     * @return the session cache
     */
    private CmsADESessionCache getSessionCache() {

        if (m_sessionCache == null) {
            m_sessionCache = CmsADESessionCache.getCache(getRequest(), getCmsObject());
        }
        return m_sessionCache;
    }

    /**
     * Returns the workplace locale.<p>
     *
     * @param cms the current OpenCms context
     *
     * @return the current users workplace locale
     */
    private Locale getWorkplaceLocale(CmsObject cms) {

        if (m_workplaceLocale == null) {
            m_workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
        }
        return m_workplaceLocale;
    }

    /**
     * Reads the content definition for the given resource and locale.<p>
     *
     * @param file the resource file
     * @param content the XML content
     * @param entityId the entity id
     * @param locale the content locale
     * @param newLocale if the locale content should be created as new
     * @param mainLocale the main language to copy in case the element language node does not exist yet
     * @param editedLocaleEntity the edited locale entity
     *
     * @return the content definition
     *
     * @throws CmsException if something goes wrong
     */
    private CmsContentDefinition readContentDefinition(CmsFile file, CmsXmlContent content, String entityId,
            Locale locale, boolean newLocale, Locale mainLocale, CmsEntity editedLocaleEntity) throws CmsException {

        long timer = 0;
        if (LOG.isDebugEnabled()) {
            timer = System.currentTimeMillis();
        }
        CmsObject cms = getCmsObject();
        List<Locale> availableLocalesList = OpenCms.getLocaleManager().getAvailableLocales(cms, file);
        if (!availableLocalesList.contains(locale)) {
            availableLocalesList.retainAll(content.getLocales());
            List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, file);
            Locale replacementLocale = OpenCms.getLocaleManager().getBestMatchingLocale(locale, defaultLocales,
                    availableLocalesList);
            LOG.info("Can't edit locale " + locale + " of file " + file.getRootPath()
                    + " because it is not configured as available locale. Using locale " + replacementLocale
                    + " instead.");
            locale = replacementLocale;
            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
        }

        if (CmsStringUtil.isEmptyOrWhitespaceOnly(entityId)) {
            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
        }
        boolean performedAutoCorrection = checkAutoCorrection(cms, content);
        if (performedAutoCorrection) {
            content.initDocument();
        }
        if (LOG.isDebugEnabled()) {
            LOG.debug(Messages.get().getBundle().key(Messages.LOG_TAKE_UNMARSHALING_TIME_1,
                    "" + (System.currentTimeMillis() - timer)));
        }
        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(cms, file, locale);
        if (LOG.isDebugEnabled()) {
            timer = System.currentTimeMillis();
        }
        visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
        if (LOG.isDebugEnabled()) {
            LOG.debug(Messages.get().getBundle().key(Messages.LOG_TAKE_VISITING_TYPES_TIME_1,
                    "" + (System.currentTimeMillis() - timer)));
        }
        CmsEntity entity = null;
        Map<String, String> syncValues = new HashMap<String, String>();
        Collection<String> skipPaths = new HashSet<String>();
        evaluateSyncLocaleValues(content, syncValues, skipPaths);
        if (content.hasLocale(locale) && newLocale) {
            // a new locale is requested, so remove the present one
            content.removeLocale(locale);
        }
        if (!content.hasLocale(locale)) {
            if ((mainLocale != null) && content.hasLocale(mainLocale)) {
                content.copyLocale(mainLocale, locale);
            } else {
                content.addLocale(cms, locale);
            }
            // sync the locale values
            if (!visitor.getLocaleSynchronizations().isEmpty() && (content.getLocales().size() > 1)) {
                for (Locale contentLocale : content.getLocales()) {
                    if (!contentLocale.equals(locale)) {
                        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
                    }
                }
            }
        }
        Element element = content.getLocaleNode(locale);
        if (LOG.isDebugEnabled()) {
            timer = System.currentTimeMillis();
        }
        entity = readEntity(content, element, locale, entityId, "", getTypeUri(content.getContentDefinition()),
                visitor, false, editedLocaleEntity);

        if (LOG.isDebugEnabled()) {
            LOG.debug(Messages.get().getBundle().key(Messages.LOG_TAKE_READING_ENTITY_TIME_1,
                    "" + (System.currentTimeMillis() - timer)));
        }
        List<String> contentLocales = new ArrayList<String>();
        for (Locale contentLocale : content.getLocales()) {
            contentLocales.add(contentLocale.toString());
        }
        Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
        TreeMap<String, String> availableLocales = new TreeMap<String, String>();
        for (Locale availableLocale : OpenCms.getLocaleManager().getAvailableLocales(cms, file)) {
            availableLocales.put(availableLocale.toString(), availableLocale.getDisplayName(workplaceLocale));
        }
        String title = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue();
        try {
            CmsGallerySearchResult searchResult = CmsGallerySearch.searchById(cms, file.getStructureId(), locale);
            title = searchResult.getTitle();
        } catch (CmsException e) {
            LOG.warn(e.getLocalizedMessage(), e);
        }
        String typeName = OpenCms.getResourceManager().getResourceType(file.getTypeId()).getTypeName();
        boolean autoUnlock = OpenCms.getWorkplaceManager().shouldAcaciaUnlock();
        Map<String, CmsEntity> entities = new HashMap<String, CmsEntity>();
        entities.put(entityId, entity);

        return new CmsContentDefinition(entityId, entities, visitor.getAttributeConfigurations(),
                visitor.getWidgetConfigurations(), visitor.getComplexWidgetData(), visitor.getTypes(),
                visitor.getTabInfos(), locale.toString(), contentLocales, availableLocales,
                visitor.getLocaleSynchronizations(), syncValues, skipPaths, title, cms.getSitePath(file), typeName,
                performedAutoCorrection, autoUnlock, getChangeHandlerScopes(content.getContentDefinition()));
    }

    /**
     * Creates a new resource according to the new link, or returns the model file informations
     * modelFileId is <code>null</code> but required.<p>
     *
     * @param newLink the new link
     * @param referenceResource the reference resource
     * @param modelFileId the model file structure id
     * @param locale the content locale
     * @param mode the content creation mode
     * @param postCreateHandler the class name for the post-create handler
     *
     * @return the content definition
     *
     * @throws CmsException if creating the resource failed
     */
    private CmsContentDefinition readContentDefnitionForNew(String newLink, CmsResource referenceResource,
            CmsUUID modelFileId, Locale locale, String mode, String postCreateHandler) throws CmsException {

        String sitePath = getCmsObject().getSitePath(referenceResource);
        String resourceType = OpenCms.getResourceManager().getResourceType(referenceResource.getTypeId())
                .getTypeName();
        String modelFile = null;
        if (modelFileId == null) {
            List<CmsResource> modelResources = CmsResourceTypeXmlContent.getModelFiles(getCmsObject(),
                    CmsResource.getFolderPath(sitePath), resourceType);
            if (!modelResources.isEmpty()) {
                List<CmsModelResourceInfo> modelInfos = CmsContainerpageService
                        .generateModelResourceList(getCmsObject(), resourceType, modelResources, locale);
                return new CmsContentDefinition(modelInfos, newLink, referenceResource.getStructureId(),
                        locale.toString());
            }
        } else if (!modelFileId.isNullUUID()) {
            modelFile = getCmsObject()
                    .getSitePath(getCmsObject().readResource(modelFileId, CmsResourceFilter.IGNORE_EXPIRATION));
        }
        String newFileName = null;
        if ((null != newLink) && newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) {

            newFileName = CmsJspTagEdit.createResource(getCmsObject(), newLink, locale, sitePath, modelFile, mode,
                    postCreateHandler);
        } else {
            newFileName = A_CmsResourceCollector.createResourceForCollector(getCmsObject(), newLink, locale,
                    sitePath, modelFile, mode, postCreateHandler);
        }
        CmsResource resource = getCmsObject().readResource(newFileName, CmsResourceFilter.IGNORE_EXPIRATION);
        CmsFile file = getCmsObject().readFile(resource);
        CmsXmlContent content = getContentDocument(file, false);
        CmsContentDefinition contentDefinition = readContentDefinition(file, content, null, locale, false, null,
                null);
        contentDefinition.setDeleteOnCancel(true);
        return contentDefinition;
    }

    /**
     * Synchronizes the locale independent fields for the given entity.<p>
     *
     * @param file the content file
     * @param content the XML content
     * @param skipPaths the paths to skip during locale synchronization
     * @param entity the entity
     *
     * @throws CmsXmlException if something goes wrong
     */
    private void synchronizeLocaleIndependentForEntity(CmsFile file, CmsXmlContent content,
            Collection<String> skipPaths, CmsEntity entity) throws CmsXmlException {

        CmsObject cms = getCmsObject();
        String entityId = entity.getId();
        Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
        CmsContentTypeVisitor visitor = null;
        CmsEntity originalEntity = null;
        if (content.getHandler().hasVisibilityHandlers()) {
            visitor = new CmsContentTypeVisitor(cms, file, contentLocale);
            visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
        }
        if (content.hasLocale(contentLocale)) {
            if ((visitor != null) && visitor.hasInvisibleFields()) {
                // we need to add invisible content values to the entity before saving
                Element element = content.getLocaleNode(contentLocale);
                originalEntity = readEntity(content, element, contentLocale, entityId, "",
                        getTypeUri(content.getContentDefinition()), visitor, true, entity);
            }
            content.removeLocale(contentLocale);
        }
        content.addLocale(cms, contentLocale);
        if ((visitor != null) && visitor.hasInvisibleFields()) {
            transferInvisibleValues(originalEntity, entity, visitor);
        }
        addEntityAttributes(cms, content, "", entity, contentLocale);
        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
    }

    /**
     * Validates the given XML content.<p>
     *
     * @param cms the cms context
     * @param structureId the structure id
     * @param content the XML content
     *
     * @return the validation result
     */
    private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content) {

        return validateContent(cms, structureId, content, null);
    }

    /**
     * Validates the given XML content.<p>
     *
     * @param cms the cms context
     * @param structureId the structure id
     * @param content the XML content
     * @param fieldNames if not null, only validation errors in paths from this set will be added to the validation result
     *
     * @return the validation result
     */
    private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content,
            Set<String> fieldNames) {

        CmsXmlContentErrorHandler errorHandler = content.validate(cms);
        Map<String, Map<String[], String>> errorsByEntity = new HashMap<String, Map<String[], String>>();

        if (errorHandler.hasErrors()) {
            boolean reallyHasErrors = false;
            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getErrors().entrySet()) {
                Map<String[], String> errors = new HashMap<String[], String>();
                for (Entry<String, String> error : localeEntry.getValue().entrySet()) {
                    I_CmsXmlContentValue value = content.getValue(error.getKey(), localeEntry.getKey());
                    if ((fieldNames == null) || fieldNames.contains(value.getPath())) {
                        errors.put(getPathElements(content, value), error.getValue());
                        reallyHasErrors = true;
                    }

                }
                if (reallyHasErrors) {
                    errorsByEntity.put(
                            CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
                            errors);
                }
            }
        }
        Map<String, Map<String[], String>> warningsByEntity = new HashMap<String, Map<String[], String>>();
        if (errorHandler.hasWarnings()) {
            boolean reallyHasErrors = false;
            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getWarnings().entrySet()) {
                Map<String[], String> warnings = new HashMap<String[], String>();
                for (Entry<String, String> warning : localeEntry.getValue().entrySet()) {
                    I_CmsXmlContentValue value = content.getValue(warning.getKey(), localeEntry.getKey());
                    if ((fieldNames == null) || fieldNames.contains(value.getPath())) {
                        warnings.put(getPathElements(content, value), warning.getValue());
                        reallyHasErrors = true;
                    }
                }
                if (reallyHasErrors) {
                    warningsByEntity.put(
                            CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
                            warnings);
                }
            }
        }
        return new CmsValidationResult(errorsByEntity, warningsByEntity);
    }

    /**
     * Writes the categories that are dynamically read/wrote by the content editor.
     *
     * @param file the file where the content is stored.
     * @param content the content.
     * @param lastEditedEntity the last edited entity
     */
    private void writeCategories(CmsFile file, CmsXmlContent content, CmsEntity lastEditedEntity) {

        // do nothing if one of the arguments is empty.
        if ((null == content) || (null == file)) {
            return;
        }

        CmsObject cms = getCmsObject();
        if (!content.getLocales().isEmpty()) {
            Locale locale = content.getLocales().iterator().next();
            CmsEntity entity = lastEditedEntity;
            List<I_CmsXmlContentValue> values = content.getValues(locale);
            for (I_CmsXmlContentValue value : values) {
                if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
                    I_CmsWidget widget = null;
                    try {
                        widget = value.getContentDefinition().getContentHandler().getWidget(value);
                    } catch (CmsXmlException e) {
                        LOG.error(e.getLocalizedMessage(), e);
                    }
                    List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
                    try {
                        categories = CmsCategoryService.getInstance().readResourceCategories(cms, file);
                    } catch (CmsException e) {
                        LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
                    }
                    if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
                        String mainCategoryPath = ((CmsCategoryWidget) widget).getStartingCategory(cms,
                                cms.getSitePath(file));
                        for (CmsCategory category : categories) {
                            if (category.getPath().startsWith(mainCategoryPath)) {
                                try {
                                    CmsCategoryService.getInstance().removeResourceFromCategory(cms,
                                            cms.getSitePath(file), category);
                                } catch (CmsException e) {
                                    LOG.error(e.getLocalizedMessage(), e);
                                }
                            }
                        }
                        if (null == entity) {
                            try {
                                CmsContentDefinition definition = readContentDefinition(file, content, "dummy",
                                        locale, false, null, null);
                                entity = definition.getEntity();
                            } catch (CmsException e) {
                                LOG.error(e.getLocalizedMessage(), e);
                            }
                        }
                        String checkedCategories = "";
                        if (null != entity) {
                            checkedCategories = CmsEntity.getValueForPath(entity, new String[] { value.getPath() });
                        }
                        List<String> checkedCategoryList = Arrays.asList(checkedCategories.split(","));
                        for (String category : checkedCategoryList) {
                            try {
                                CmsCategoryService.getInstance().addResourceToCategory(cms, cms.getSitePath(file),
                                        CmsCategoryService.getInstance().getCategory(cms, category));
                            } catch (CmsException e) {
                                LOG.error(e.getLocalizedMessage(), e);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Writes the xml content to the vfs and re-initializes the member variables.<p>
     *
     * @param cms the cms context
     * @param file the file to write to
     * @param content the content
     * @param encoding the file encoding
     *
     * @return the content
     *
     * @throws CmsException if writing the file fails
     */
    private CmsXmlContent writeContent(CmsObject cms, CmsFile file, CmsXmlContent content, String encoding)
            throws CmsException {

        String decodedContent = content.toString();
        try {
            file.setContents(decodedContent.getBytes(encoding));
        } catch (UnsupportedEncodingException e) {
            throw new CmsException(
                    org.opencms.workplace.editors.Messages.get().container(
                            org.opencms.workplace.editors.Messages.ERR_INVALID_CONTENT_ENC_1, file.getRootPath()),
                    e);
        }
        // the file content might have been modified during the write operation
        file = cms.writeFile(file);
        return CmsXmlContentFactory.unmarshal(cms, file);
    }

}