001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.contenteditor;
029
030import org.opencms.acacia.shared.CmsAttributeConfiguration;
031import org.opencms.acacia.shared.CmsEntity;
032import org.opencms.acacia.shared.CmsEntityAttribute;
033import org.opencms.acacia.shared.CmsEntityHtml;
034import org.opencms.acacia.shared.CmsTabInfo;
035import org.opencms.acacia.shared.CmsType;
036import org.opencms.acacia.shared.CmsValidationResult;
037import org.opencms.ade.configuration.CmsADEConfigData;
038import org.opencms.ade.configuration.CmsResourceTypeConfig;
039import org.opencms.ade.containerpage.CmsContainerpageService;
040import org.opencms.ade.containerpage.CmsElementUtil;
041import org.opencms.ade.containerpage.shared.CmsCntPageData;
042import org.opencms.ade.containerpage.shared.CmsContainer;
043import org.opencms.ade.containerpage.shared.CmsContainerElement;
044import org.opencms.ade.containerpage.shared.CmsFormatterConfig;
045import org.opencms.ade.contenteditor.shared.CmsContentDefinition;
046import org.opencms.ade.contenteditor.shared.CmsEditHandlerData;
047import org.opencms.ade.contenteditor.shared.CmsEditorConstants;
048import org.opencms.ade.contenteditor.shared.CmsSaveResult;
049import org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService;
050import org.opencms.file.CmsFile;
051import org.opencms.file.CmsObject;
052import org.opencms.file.CmsPropertyDefinition;
053import org.opencms.file.CmsResource;
054import org.opencms.file.CmsResourceFilter;
055import org.opencms.file.collectors.A_CmsResourceCollector;
056import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler;
057import org.opencms.file.types.CmsResourceTypeXmlContent;
058import org.opencms.file.types.I_CmsResourceType;
059import org.opencms.flex.CmsFlexController;
060import org.opencms.gwt.CmsGwtService;
061import org.opencms.gwt.CmsIconUtil;
062import org.opencms.gwt.CmsRpcException;
063import org.opencms.gwt.shared.CmsGwtConstants;
064import org.opencms.gwt.shared.CmsModelResourceInfo;
065import org.opencms.i18n.CmsEncoder;
066import org.opencms.i18n.CmsLocaleManager;
067import org.opencms.i18n.CmsMessages;
068import org.opencms.json.JSONObject;
069import org.opencms.jsp.CmsJspTagEdit;
070import org.opencms.main.CmsException;
071import org.opencms.main.CmsLog;
072import org.opencms.main.CmsRuntimeException;
073import org.opencms.main.OpenCms;
074import org.opencms.relations.CmsCategory;
075import org.opencms.relations.CmsCategoryService;
076import org.opencms.search.galleries.CmsGallerySearch;
077import org.opencms.search.galleries.CmsGallerySearchResult;
078import org.opencms.security.CmsAccessControlEntry;
079import org.opencms.util.CmsFileUtil;
080import org.opencms.util.CmsPair;
081import org.opencms.util.CmsRequestUtil;
082import org.opencms.util.CmsStringUtil;
083import org.opencms.util.CmsUUID;
084import org.opencms.widgets.CmsCalendarWidget;
085import org.opencms.widgets.CmsCategoryWidget;
086import org.opencms.widgets.CmsCheckboxWidget;
087import org.opencms.widgets.CmsComboWidget;
088import org.opencms.widgets.CmsGroupWidget;
089import org.opencms.widgets.CmsInputWidget;
090import org.opencms.widgets.CmsMultiSelectWidget;
091import org.opencms.widgets.CmsRadioSelectWidget;
092import org.opencms.widgets.CmsSelectComboWidget;
093import org.opencms.widgets.CmsSelectWidget;
094import org.opencms.widgets.CmsVfsFileWidget;
095import org.opencms.widgets.I_CmsADEWidget;
096import org.opencms.widgets.I_CmsWidget;
097import org.opencms.workplace.CmsDialog;
098import org.opencms.workplace.CmsWorkplace;
099import org.opencms.workplace.editors.CmsEditor;
100import org.opencms.workplace.editors.CmsEditorCssHandlerDefault;
101import org.opencms.workplace.editors.CmsXmlContentEditor;
102import org.opencms.workplace.editors.directedit.I_CmsEditHandler;
103import org.opencms.xml.CmsXmlContentDefinition;
104import org.opencms.xml.CmsXmlDisplayOrderPathComparator;
105import org.opencms.xml.CmsXmlEntityResolver;
106import org.opencms.xml.CmsXmlException;
107import org.opencms.xml.CmsXmlUtils;
108import org.opencms.xml.I_CmsXmlDocument;
109import org.opencms.xml.containerpage.CmsADESessionCache;
110import org.opencms.xml.containerpage.CmsContainerElementBean;
111import org.opencms.xml.containerpage.I_CmsFormatterBean;
112import org.opencms.xml.content.CmsXmlContent;
113import org.opencms.xml.content.CmsXmlContentErrorHandler;
114import org.opencms.xml.content.CmsXmlContentFactory;
115import org.opencms.xml.content.CmsXmlContentProperty;
116import org.opencms.xml.content.CmsXmlContentPropertyHelper;
117import org.opencms.xml.content.I_CmsXmlContentEditorChangeHandler;
118import org.opencms.xml.content.I_CmsXmlContentHandler.DisplayType;
119import org.opencms.xml.types.CmsXmlAccessRestrictionValue;
120import org.opencms.xml.types.CmsXmlDynamicCategoryValue;
121import org.opencms.xml.types.I_CmsXmlContentValue;
122import org.opencms.xml.types.I_CmsXmlSchemaType;
123
124import java.io.UnsupportedEncodingException;
125import java.util.ArrayList;
126import java.util.Arrays;
127import java.util.Collection;
128import java.util.Collections;
129import java.util.HashMap;
130import java.util.HashSet;
131import java.util.LinkedHashMap;
132import java.util.List;
133import java.util.Locale;
134import java.util.Map;
135import java.util.Map.Entry;
136import java.util.Set;
137import java.util.TreeMap;
138
139import javax.servlet.http.HttpServletRequest;
140
141import org.apache.commons.logging.Log;
142
143import org.dom4j.Element;
144
145import com.google.common.base.Suppliers;
146import com.google.common.collect.Sets;
147
148/**
149 * Service to provide entity persistence within OpenCms. <p>
150 */
151public class CmsContentService extends CmsGwtService implements I_CmsContentService {
152
153    /** Request context attribute to mark a writeFile() triggered by the user saving in the content editor. */
154    public static final String ATTR_EDITOR_SAVING = "__EDITOR_SAVING";
155
156    /** The logger for this class. */
157    protected static final Log LOG = CmsLog.getLog(CmsContentService.class);
158
159    /** The type name prefix. */
160    static final String TYPE_NAME_PREFIX = "http://opencms.org/types/";
161
162    /** The RDFA attributes string. */
163    private static final String RDFA_ATTRIBUTES = CmsGwtConstants.ATTR_DATA_ID
164        + "=\"%1$s\" "
165        + CmsGwtConstants.ATTR_DATA_FIELD
166        + "=\"%2$s\"";
167
168    /** The serial version id. */
169    private static final long serialVersionUID = 7873052619331296648L;
170
171    /** The setting type name. */
172    private static final String SETTING_TYPE_NAME = "###SETTING_TYPE###";
173
174    /** The settings attribute name prefix. */
175    private static final String SETTINGS_ATTRIBUTE_NAME_PREFIX = "SETTING:::";
176
177    /** The attribute name used for the client id. */
178    private static final String SETTINGS_CLIENT_ID_ATTRIBUTE = "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + "CLIENT_ID:::";
179
180    /** The settings validation rule type name. */
181    private static final String SETTINGS_RULE_TYPE_ERROR = "error";
182
183    /** Mapping client widget names to server side widget classes. */
184    private static final Map<String, Class<? extends I_CmsADEWidget>> WIDGET_MAPPINGS = new HashMap<>();
185
186    /** The session cache. */
187    private CmsADESessionCache m_sessionCache;
188
189    /** The current users workplace locale. */
190    private Locale m_workplaceLocale;
191
192    static {
193        WIDGET_MAPPINGS.put("string", CmsInputWidget.class);
194        WIDGET_MAPPINGS.put("select", CmsSelectWidget.class);
195        WIDGET_MAPPINGS.put("multicheck", org.opencms.widgets.CmsMultiSelectWidget.class);
196        WIDGET_MAPPINGS.put("selectcombo", CmsSelectComboWidget.class);
197        WIDGET_MAPPINGS.put("checkbox", CmsCheckboxWidget.class);
198        WIDGET_MAPPINGS.put("combo", CmsComboWidget.class);
199        WIDGET_MAPPINGS.put("datebox", CmsCalendarWidget.class);
200        WIDGET_MAPPINGS.put("gallery", CmsVfsFileWidget.class);
201        WIDGET_MAPPINGS.put("multiselectbox", CmsMultiSelectWidget.class);
202        WIDGET_MAPPINGS.put("radio", CmsRadioSelectWidget.class);
203        WIDGET_MAPPINGS.put("groupselection", CmsGroupWidget.class);
204    }
205
206    /**
207     * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p>
208     *
209     * @param cms The CmsObject of the current request context.
210     * @param newLink A string, specifying where which new content should be created.
211     * @param locale The locale for which the
212     * @param referenceSitePath site path of the currently edited content.
213     * @param modelFileSitePath site path of the model file
214     * @param mode optional creation mode
215     * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created.
216     *
217     * @return The site-path of the newly created resource.
218     * @throws CmsException if something goes wrong
219     */
220    public static String defaultCreateResourceToEdit(
221        CmsObject cms,
222        String newLink,
223        Locale locale,
224        String referenceSitePath,
225        String modelFileSitePath,
226        String mode,
227        String postCreateHandler)
228    throws CmsException {
229
230        String newFileName;
231        if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) {
232
233            newFileName = CmsJspTagEdit.createResource(
234                cms,
235                newLink,
236                locale,
237                referenceSitePath,
238                modelFileSitePath,
239                mode,
240                postCreateHandler);
241        } else {
242            newFileName = A_CmsResourceCollector.createResourceForCollector(
243                cms,
244                newLink,
245                locale,
246                referenceSitePath,
247                modelFileSitePath,
248                mode,
249                postCreateHandler);
250        }
251        return newFileName;
252    }
253
254    /**
255     * Returns the entity attribute name representing the given content value.<p>
256     *
257     * @param contentValue the content value
258     *
259     * @return the attribute name
260     */
261    public static String getAttributeName(I_CmsXmlContentValue contentValue) {
262
263        return getTypeUri(contentValue.getContentDefinition()) + "/" + contentValue.getName();
264    }
265
266    /**
267     * Returns the entity attribute name to use for this element.<p>
268     *
269     * @param elementName the element name
270     * @param parentType the parent type
271     *
272     * @return the attribute name
273     */
274    public static String getAttributeName(String elementName, String parentType) {
275
276        return parentType + "/" + elementName;
277    }
278
279    /**
280     * Returns the entity id to the given content value.<p>
281     *
282     * @param contentValue the content value
283     *
284     * @return the entity id
285     */
286    public static String getEntityId(I_CmsXmlContentValue contentValue) {
287
288        String result = CmsContentDefinition.uuidToEntityId(
289            contentValue.getDocument().getFile().getStructureId(),
290            contentValue.getLocale().toString());
291        String valuePath = contentValue.getPath();
292        if (valuePath.contains("/")) {
293            result += "/" + valuePath.substring(0, valuePath.lastIndexOf("/"));
294        }
295        if (contentValue.isChoiceOption()) {
296            result += "/"
297                + CmsType.CHOICE_ATTRIBUTE_NAME
298                + "_"
299                + contentValue.getName()
300                + "["
301                + contentValue.getXmlIndex()
302                + "]";
303        }
304        return result;
305    }
306
307    /**
308     * Returns the RDF annotations required for in line editing.<p>
309     *
310     * @param value the XML content value
311     *
312     * @return the RDFA
313     */
314    public static String getRdfaAttributes(I_CmsXmlContentValue value) {
315
316        String path = "";
317        String elementPath = value.getPath();
318        if (elementPath.contains("/")) {
319            path += "/" + removePathIndexes(elementPath.substring(0, elementPath.lastIndexOf("/")) + ":");
320        }
321        path += CmsContentService.getAttributeName(value);
322        return String.format(RDFA_ATTRIBUTES, CmsContentService.getEntityId(value), path);
323    }
324
325    /**
326     * Returns the RDF annotations required for in line editing.<p>
327     *
328     * @param parentValue the parent XML content value
329     * @param childNames the child attribute names separated by '|'
330     *
331     * @return the RDFA
332     */
333    public static String getRdfaAttributes(I_CmsXmlContentValue parentValue, String childNames) {
334
335        String id = CmsContentDefinition.uuidToEntityId(
336            parentValue.getDocument().getFile().getStructureId(),
337            parentValue.getLocale().toString()) + "/" + parentValue.getPath();
338        String path = "";
339        String[] children = childNames.split("\\|");
340        for (int i = 0; i < children.length; i++) {
341            I_CmsXmlSchemaType schemaType = parentValue.getContentDefinition().getSchemaType(
342                parentValue.getName() + "/" + children[i]);
343            if (schemaType != null) {
344                if (i > 0) {
345                    path += " ";
346                }
347                String typePath = parentValue.getPath();
348                path += "/" + removePathIndexes(typePath) + ":";
349                path += getTypeUri(schemaType.getContentDefinition()) + "/" + children[i];
350            }
351        }
352        return String.format(RDFA_ATTRIBUTES, id, path);
353    }
354
355    /**
356     * Returns the RDF annotations required for in line editing.<p>
357     *
358     * @param document the parent XML document
359     * @param contentLocale the content locale
360     * @param elementPath the element xpath to get the RDF annotation for
361     *
362     * @return the RDFA
363     */
364    public static String getRdfaAttributes(I_CmsXmlDocument document, Locale contentLocale, String elementPath) {
365
366        I_CmsXmlSchemaType schemaType = document.getContentDefinition().getSchemaType(elementPath);
367        if (schemaType != null) {
368            String path = "";
369            if (elementPath.contains("/")) {
370                path += "/" + removePathIndexes(elementPath.substring(0, elementPath.lastIndexOf("/")) + ":");
371            }
372            path += getTypeUri(schemaType.getContentDefinition()) + "/" + elementPath;
373            return String.format(
374                RDFA_ATTRIBUTES,
375                CmsContentDefinition.uuidToEntityId(document.getFile().getStructureId(), contentLocale.toString()),
376                path);
377        } else {
378            return "";
379        }
380    }
381
382    /**
383     * Returns the type URI.<p>
384     *
385     * @param xmlContentDefinition the type content definition
386     *
387     * @return the type URI
388     */
389    public static String getTypeUri(CmsXmlContentDefinition xmlContentDefinition) {
390
391        return xmlContentDefinition.getSchemaLocation() + "/" + xmlContentDefinition.getTypeName();
392    }
393
394    /**
395     * Fetches the initial content definition.<p>
396     *
397     * @param request the current request
398     *
399     * @return the initial content definition
400     *
401     * @throws CmsRpcException if something goes wrong
402     */
403    public static CmsContentDefinition prefetch(HttpServletRequest request) throws CmsRpcException {
404
405        CmsContentService srv = new CmsContentService();
406        srv.setCms(CmsFlexController.getCmsObject(request));
407        srv.setRequest(request);
408        CmsContentDefinition result = null;
409        try {
410            result = srv.prefetch();
411        } finally {
412            srv.clearThreadStorage();
413        }
414        return result;
415    }
416
417    /**
418     * Removes the XPath indexes from the given path.<p>
419     *
420     * @param path the path
421     *
422     * @return the changed path
423     */
424    private static String removePathIndexes(String path) {
425
426        return path.replaceAll("\\[.*\\]", "");
427    }
428
429    /**
430     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#callEditorChangeHandlers(java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Collection)
431     */
432    public CmsContentDefinition callEditorChangeHandlers(
433        String entityId,
434        CmsEntity editedLocaleEntity,
435        Collection<String> skipPaths,
436        Collection<String> changedScopes)
437    throws CmsRpcException {
438
439        CmsContentDefinition result = null;
440        CmsObject cms = getCmsObject();
441        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
442            cms,
443            cms.getRequestContext().getRootUri());
444        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(editedLocaleEntity.getId());
445        if (structureId != null) {
446
447            CmsResource resource = null;
448            Locale locale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
449            try {
450                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
451                ensureLock(resource);
452                CmsFile file = cms.readFile(resource);
453                CmsXmlContent content = getContentDocument(file, true).clone();
454                checkAutoCorrection(cms, content);
455                synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
456                for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler().getEditorChangeHandlers(
457                    false)) {
458                    Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition());
459                    if (!Collections.disjoint(changedScopes, handlerScopes)) {
460                        handler.handleChange(cms, content, locale, changedScopes);
461                    }
462                }
463                result = readContentDefinition(
464                    file,
465                    content,
466                    entityId,
467                    null,
468                    locale,
469                    false,
470                    null,
471                    editedLocaleEntity,
472                    Collections.emptyMap(),
473                    config);
474            } catch (Exception e) {
475                error(e);
476            }
477        }
478        return result;
479    }
480
481    /**
482     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#copyLocale(java.util.Collection, org.opencms.acacia.shared.CmsEntity)
483     */
484    public void copyLocale(Collection<String> locales, CmsEntity sourceLocale) throws CmsRpcException {
485
486        try {
487            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(sourceLocale.getId());
488
489            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
490            CmsFile file = getCmsObject().readFile(resource);
491            CmsXmlContent content = getSessionCache().getCacheXmlContent(structureId);
492            synchronizeLocaleIndependentForEntity(file, content, Collections.<String> emptyList(), sourceLocale);
493            Locale sourceContentLocale = CmsLocaleManager.getLocale(
494                CmsContentDefinition.getLocaleFromId(sourceLocale.getId()));
495            for (String loc : locales) {
496                Locale targetLocale = CmsLocaleManager.getLocale(loc);
497                if (content.hasLocale(targetLocale)) {
498                    content.removeLocale(targetLocale);
499                }
500                content.copyLocale(sourceContentLocale, targetLocale);
501            }
502        } catch (Throwable t) {
503            error(t);
504        }
505    }
506
507    /**
508     * @see org.opencms.gwt.CmsGwtService#getCmsObject()
509     */
510    @Override
511    public CmsObject getCmsObject() {
512
513        CmsObject result = super.getCmsObject();
514        // disable link invalidation in the editor
515        result.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE);
516        return result;
517    }
518
519    /**
520     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#loadContentDefinition(java.lang.String)
521     */
522    public CmsContentDefinition loadContentDefinition(String entityId) throws CmsRpcException {
523
524        throw new CmsRpcException(new UnsupportedOperationException());
525    }
526
527    /**
528     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadDefinition(java.lang.String, java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Map)
529     */
530    public CmsContentDefinition loadDefinition(
531
532        String entityId,
533        String clientId,
534        CmsEntity editedLocaleEntity,
535        Collection<String> skipPaths,
536        Map<String, String> settingPresets)
537    throws CmsRpcException {
538
539        CmsContentDefinition definition = null;
540        try {
541            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
542            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
543            CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
544                getCmsObject(),
545                getCmsObject().getRequestContext().getRootUri());
546
547            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
548            CmsFile file = getCmsObject().readFile(resource);
549            CmsXmlContent content = getContentDocument(file, true);
550            if (editedLocaleEntity != null) {
551                synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
552            }
553            definition = readContentDefinition(
554                file,
555                content,
556                CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()),
557                clientId,
558                contentLocale,
559                false,
560                null,
561                editedLocaleEntity,
562                settingPresets,
563                config);
564        } catch (Exception e) {
565            error(e);
566        }
567        return definition;
568    }
569
570    /**
571     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadInitialDefinition(java.lang.String, java.lang.String, java.lang.String, org.opencms.util.CmsUUID, java.lang.String, java.lang.String, java.lang.String, java.lang.String, org.opencms.ade.contenteditor.shared.CmsEditHandlerData, java.util.Map, java.lang.String)
572     */
573    public CmsContentDefinition loadInitialDefinition(
574
575        String entityId,
576        String clientId,
577        String newLink,
578        CmsUUID modelFileId,
579        String editContext,
580        String mainLocale,
581        String mode,
582        String postCreateHandler,
583        CmsEditHandlerData editHandlerDataForNew,
584        Map<String, String> settingPresets,
585        String editorStylesheet)
586    throws CmsRpcException {
587
588        CmsObject cms = getCmsObject();
589        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
590            cms,
591            cms.getRequestContext().getRootUri());
592        CmsContentDefinition result = null;
593        getCmsObject().getRequestContext().setAttribute(CmsXmlContentEditor.ATTRIBUTE_EDITCONTEXT, editContext);
594        if (editorStylesheet != null) {
595            getCmsObject().getRequestContext().setAttribute(
596                CmsEditorCssHandlerDefault.ATTRIBUTE_EDITOR_STYLESHEET,
597                editorStylesheet);
598        }
599        try {
600            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
601            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
602            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
603            getSessionCache().clearDynamicValues();
604            getSessionCache().uncacheXmlContent(structureId);
605            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(newLink)) {
606                result = readContentDefinitionForNew(
607                    newLink,
608                    resource,
609                    modelFileId,
610                    contentLocale,
611                    mode,
612                    postCreateHandler,
613                    editHandlerDataForNew,
614                    settingPresets);
615            } else {
616                CmsFile file = getCmsObject().readFile(resource);
617                CmsXmlContent content = getContentDocument(file, false);
618                result = readContentDefinition(
619                    file,
620                    content,
621                    CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()),
622                    clientId,
623                    contentLocale,
624                    false,
625                    mainLocale != null ? CmsLocaleManager.getLocale(mainLocale) : null,
626                    null,
627                    settingPresets,
628                    config);
629            }
630        } catch (Throwable t) {
631            error(t);
632        }
633        return result;
634    }
635
636    /**
637     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#loadNewDefinition(java.lang.String, java.lang.String, org.opencms.acacia.shared.CmsEntity, java.util.Collection, java.util.Map)
638     */
639    public CmsContentDefinition loadNewDefinition(
640
641        String entityId,
642        String clientId,
643        CmsEntity editedLocaleEntity,
644        Collection<String> skipPaths,
645        Map<String, String> settingPresets)
646    throws CmsRpcException {
647
648        CmsObject cms = getCmsObject();
649        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
650            cms,
651            cms.getRequestContext().getRootUri());
652        CmsContentDefinition definition = null;
653        try {
654            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
655            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
656            Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
657            CmsFile file = getCmsObject().readFile(resource);
658            CmsXmlContent content = getContentDocument(file, true);
659            synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedLocaleEntity);
660            definition = readContentDefinition(
661                file,
662                content,
663                CmsContentDefinition.uuidToEntityId(structureId, contentLocale.toString()),
664                clientId,
665                contentLocale,
666                true,
667                null,
668                editedLocaleEntity,
669                settingPresets,
670                config);
671        } catch (Exception e) {
672            error(e);
673        }
674        return definition;
675    }
676
677    /**
678     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#prefetch()
679     */
680    public CmsContentDefinition prefetch() throws CmsRpcException {
681
682        String paramResource = getRequest().getParameter(CmsDialog.PARAM_RESOURCE);
683        String paramDirectEdit = getRequest().getParameter(CmsEditor.PARAM_DIRECTEDIT);
684        boolean isDirectEdit = false;
685        if (paramDirectEdit != null) {
686            isDirectEdit = Boolean.parseBoolean(paramDirectEdit);
687        }
688        String paramNewLink = getRequest().getParameter(CmsXmlContentEditor.PARAM_NEWLINK);
689        boolean createNew = false;
690        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramNewLink)) {
691            createNew = true;
692            paramNewLink = decodeNewLink(paramNewLink);
693        }
694        String paramLocale = getRequest().getParameter(CmsEditor.PARAM_ELEMENTLANGUAGE);
695        Locale locale = null;
696        CmsObject cms = getCmsObject();
697        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramResource)) {
698
699            // not necessary in most cases, but some old dialogs pass the path in encoded form
700            paramResource = CmsEncoder.decode(paramResource);
701
702            try {
703                CmsResource resource = cms.readResource(paramResource, CmsResourceFilter.IGNORE_EXPIRATION);
704
705                if (OpenCms.getADEManager().isEditorRestricted(cms, resource)) {
706                    // Context menus / buttons for editing the file should be disabled if above condition is true.
707                    // You only get here if you directly open the editor URL, so this does not need
708                    // a particularly nice error message
709                    throw new CmsRuntimeException(
710                        org.opencms.ade.contenteditor.Messages.get().container(
711                            org.opencms.ade.contenteditor.Messages.ERR_EDITOR_RESTRICTED_0));
712                }
713                if (CmsResourceTypeXmlContent.isXmlContent(resource) || createNew) {
714                    CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(cms, resource.getRootPath());
715                    boolean reused = false;
716                    try {
717                        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource);
718                        if (type != null) {
719                            String typeStr = type.getTypeName();
720                            CmsResourceTypeConfig typeConfig = config.getResourceType(typeStr);
721                            if ((typeConfig != null) && typeConfig.isCheckReuse()) {
722                                if (OpenCms.getADEManager().getOfflineElementUses(resource).limit(2).count() > 1) {
723                                    reused = true;
724                                }
725                            }
726                        }
727                    } catch (Exception e) {
728                        LOG.info(e.getLocalizedMessage(), e);
729                    }
730                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramLocale)) {
731                        locale = CmsLocaleManager.getLocale(paramLocale);
732                    }
733                    CmsContentDefinition result;
734                    getSessionCache().clearDynamicValues();
735                    if (createNew) {
736                        if (locale == null) {
737                            locale = OpenCms.getLocaleManager().getDefaultLocale(cms, paramResource);
738                        }
739                        CmsUUID modelFileId = null;
740                        String paramModelFile = getRequest().getParameter(CmsWorkplace.PARAM_MODELFILE);
741
742                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(paramModelFile)) {
743                            modelFileId = cms.readResource(paramModelFile).getStructureId();
744                        }
745
746                        String mode = getRequest().getParameter(CmsEditorConstants.PARAM_MODE);
747                        String postCreateHandler = getRequest().getParameter(
748                            CmsEditorConstants.PARAM_POST_CREATE_HANDLER);
749                        result = readContentDefinitionForNew(
750                            paramNewLink,
751                            resource,
752                            modelFileId,
753                            locale,
754                            mode,
755                            postCreateHandler,
756                            null,
757                            Collections.emptyMap());
758                    } else {
759
760                        CmsFile file = cms.readFile(resource);
761                        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
762                        getSessionCache().setCacheXmlContent(resource.getStructureId(), content);
763                        if (locale == null) {
764                            locale = OpenCms.getLocaleManager().getBestAvailableLocaleForXmlContent(
765                                getCmsObject(),
766                                resource,
767                                content);
768                        }
769                        result = readContentDefinition(
770                            file,
771                            content,
772                            null,
773                            null,
774                            locale,
775                            false,
776                            null,
777                            null,
778                            Collections.emptyMap(),
779                            null);
780                    }
781                    result.setDirectEdit(isDirectEdit);
782                    result.setReusedElement(reused);
783                    return result;
784                }
785            } catch (Throwable e) {
786                error(e);
787            }
788        }
789        return null;
790    }
791
792    /**
793     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveAndDeleteEntities(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.util.List, java.util.Collection, java.lang.String, boolean, boolean)
794     */
795    public CmsSaveResult saveAndDeleteEntities(
796        CmsEntity lastEditedEntity,
797        String clientId,
798        List<String> deletedEntities,
799        Collection<String> skipPaths,
800        String lastEditedLocale,
801        boolean clearOnSuccess,
802        boolean failOnWarnings)
803    throws CmsRpcException {
804
805        CmsUUID structureId = null;
806        if (lastEditedEntity != null) {
807            structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId());
808        }
809        if ((structureId == null) && !deletedEntities.isEmpty()) {
810            structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0));
811        }
812        CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration(
813            getCmsObject(),
814            getCmsObject().getRequestContext().getRootUri());
815        if (structureId != null) {
816            CmsObject cms = getCmsObject();
817            CmsResource resource = null;
818            try {
819                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
820                ensureLock(resource);
821                CmsFile file = cms.readFile(resource);
822                CmsXmlContent content = getContentDocument(file, true);
823                checkAutoCorrection(cms, content);
824                if (lastEditedEntity != null) {
825                    synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
826                }
827                for (String deleteId : deletedEntities) {
828                    Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId));
829                    if (content.hasLocale(contentLocale)) {
830                        content.removeLocale(contentLocale);
831                    }
832                }
833                CmsValidationResult validationResult = validateContent(cms, structureId, content);
834                if (validationResult.hasErrors() || (failOnWarnings && validationResult.hasWarnings())) {
835                    Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> sortedIssues = getValidationIssues(
836                        cms,
837                        content,
838                        validationResult);
839                    return new CmsSaveResult(false, validationResult, failOnWarnings, sortedIssues);
840                }
841                boolean hasChangedSettings = false;
842                if ((clientId != null) && (lastEditedEntity != null)) {
843                    CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId);
844                    I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement);
845                    if ((formatter != null)
846                        && formatter.isAllowsSettingsInEditor()
847                        && !formatter.getSettings(configData).isEmpty()) {
848                        Locale locale = CmsLocaleManager.getLocale(lastEditedLocale);
849                        Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings(
850                            cms,
851                            configData,
852                            formatter,
853                            containerElement.getResource(),
854                            locale,
855                            getRequest());
856                        validateSettings(lastEditedEntity, validationResult, settingsConfig);
857                        if (validationResult.hasErrors() || (failOnWarnings && validationResult.hasWarnings())) {
858                            Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> sortedIssues = getValidationIssues(
859                                cms,
860                                content,
861                                validationResult);
862                            return new CmsSaveResult(false, validationResult, failOnWarnings, sortedIssues);
863                        }
864
865                        List<I_CmsFormatterBean> nestedFormatters = OpenCms.getADEManager().getNestedFormatters(
866                            cms,
867                            configData,
868                            containerElement.getResource(),
869                            locale,
870                            getRequest());
871                        hasChangedSettings = saveSettings(
872                            lastEditedEntity,
873                            containerElement,
874                            settingsConfig,
875                            nestedFormatters);
876
877                    }
878                }
879                CmsAccessRestrictionInfo restrictionInfo = CmsAccessRestrictionInfo.getRestrictionInfo(
880                    cms,
881                    content.getContentDefinition());
882
883                if (restrictionInfo != null) {
884                    // the value from the editor is still stored in the content value object, so we can use it here, even if it is not written to the XML content file in the end.
885                    I_CmsXmlContentValue restrictionValue = content.getValue(
886                        restrictionInfo.getPath(),
887                        CmsLocaleManager.getLocale(lastEditedLocale));
888                    boolean restricted = false;
889                    if (restrictionValue != null) {
890                        restricted = Boolean.parseBoolean(restrictionValue.getStringValue(cms));
891                    }
892                    cms.setRestricted(content.getFile(), restrictionInfo.getGroup().getName(), restricted);
893                }
894
895                writeCategories(file, content, lastEditedEntity);
896
897                writeContent(cms, file, content, getFileEncoding(cms, file));
898
899                // update offline indices
900                OpenCms.getSearchManager().updateOfflineIndexes();
901                if (clearOnSuccess) {
902                    tryUnlock(resource);
903                    getSessionCache().uncacheXmlContent(structureId);
904                }
905                return new CmsSaveResult(hasChangedSettings, null, false, null);
906            } catch (Exception e) {
907                if (resource != null) {
908                    tryUnlock(resource);
909                    getSessionCache().uncacheXmlContent(structureId);
910                }
911                error(e);
912            }
913        }
914        return null;
915    }
916
917    /**
918     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntities(java.util.List)
919     */
920    public CmsValidationResult saveEntities(List<CmsEntity> entities) {
921
922        throw new UnsupportedOperationException();
923    }
924
925    /**
926     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#saveEntity(org.opencms.acacia.shared.CmsEntity)
927     */
928    public CmsValidationResult saveEntity(CmsEntity entity) {
929
930        throw new UnsupportedOperationException();
931    }
932
933    /**
934     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#saveValue(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
935     */
936    public String saveValue(String contentId, String contentPath, String localeString, String newValue)
937    throws CmsRpcException {
938
939        OpenCms.getLocaleManager();
940        Locale locale = CmsLocaleManager.getLocale(localeString);
941
942        try {
943            CmsObject cms = getCmsObject();
944            CmsResource element = cms.readResource(new CmsUUID(contentId), CmsResourceFilter.IGNORE_EXPIRATION);
945            ensureLock(element);
946            CmsFile elementFile = cms.readFile(element);
947            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, elementFile);
948            I_CmsXmlContentValue value = content.getValue(contentPath, locale);
949            value.setStringValue(cms, newValue);
950            for (I_CmsXmlContentEditorChangeHandler handler : content.getContentDefinition().getContentHandler().getEditorChangeHandlers(
951                false)) {
952                Set<String> handlerScopes = evaluateScope(handler.getScope(), content.getContentDefinition());
953                if (handlerScopes.contains(contentPath)) {
954                    handler.handleChange(cms, content, locale, Collections.singletonList(contentPath));
955                }
956            }
957            content.synchronizeLocaleIndependentValues(cms, Collections.<String> emptyList(), locale);
958            byte[] newData = content.marshal();
959            elementFile.setContents(newData);
960            cms.writeFile(elementFile);
961            tryUnlock(elementFile);
962            return "";
963        } catch (Exception e) {
964            error(e);
965            return null;
966        }
967
968    }
969
970    /**
971     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#updateEntityHtml(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.lang.String)
972     */
973    public CmsEntityHtml updateEntityHtml(CmsEntity entity, String contextUri, String htmlContextInfo)
974    throws Exception {
975
976        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entity.getId());
977        if (structureId != null) {
978            CmsObject cms = getCmsObject();
979            try {
980                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
981                CmsFile file = cms.readFile(resource);
982                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
983                String entityId = entity.getId();
984                Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
985                if (content.hasLocale(contentLocale)) {
986                    content.removeLocale(contentLocale);
987                }
988                content.addLocale(cms, contentLocale);
989                addEntityAttributes(cms, content, "", entity, contentLocale);
990                CmsValidationResult validationResult = validateContent(cms, structureId, content);
991                String htmlContent = null;
992                if (!validationResult.hasErrors()) {
993                    file.setContents(content.marshal());
994
995                    JSONObject contextInfo = new JSONObject(htmlContextInfo);
996                    String containerName = contextInfo.getString(CmsCntPageData.JSONKEY_NAME);
997                    String containerType = contextInfo.getString(CmsCntPageData.JSONKEY_TYPE);
998                    int containerWidth = contextInfo.getInt(CmsCntPageData.JSONKEY_WIDTH);
999                    int maxElements = contextInfo.getInt(CmsCntPageData.JSONKEY_MAXELEMENTS);
1000                    boolean detailView = contextInfo.getBoolean(CmsCntPageData.JSONKEY_DETAILVIEW);
1001                    boolean isDetailViewContainer = contextInfo.getBoolean(
1002                        CmsCntPageData.JSONKEY_ISDETAILVIEWCONTAINER);
1003                    JSONObject presets = contextInfo.getJSONObject(CmsCntPageData.JSONKEY_PRESETS);
1004                    HashMap<String, String> presetsMap = new HashMap<String, String>();
1005                    for (String key : presets.keySet()) {
1006                        String val = presets.getString(key);
1007                        presetsMap.put(key, val);
1008                    }
1009                    CmsContainer container = new CmsContainer(
1010                        containerName,
1011                        containerType,
1012                        null,
1013                        containerWidth,
1014                        maxElements,
1015                        isDetailViewContainer,
1016                        detailView,
1017                        true,
1018                        Collections.<CmsContainerElement> emptyList(),
1019                        null,
1020                        null,
1021                        presetsMap);
1022                    CmsUUID detailContentId = null;
1023                    if (contextInfo.has(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)) {
1024                        detailContentId = new CmsUUID(contextInfo.getString(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID));
1025                    }
1026                    CmsElementUtil elementUtil = new CmsElementUtil(
1027                        cms,
1028                        contextUri,
1029                        detailContentId,
1030                        getThreadLocalRequest(),
1031                        getThreadLocalResponse(),
1032                        contentLocale);
1033                    htmlContent = elementUtil.getContentByContainer(
1034                        file,
1035                        contextInfo.getString(CmsCntPageData.JSONKEY_ELEMENT_ID),
1036                        container);
1037                }
1038                return new CmsEntityHtml(htmlContent, validationResult);
1039
1040            } catch (Exception e) {
1041                error(e);
1042            }
1043        }
1044        return null;
1045    }
1046
1047    /**
1048     * @see org.opencms.ade.contenteditor.shared.rpc.I_CmsContentService#validateEntities(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.util.List, java.util.Collection, java.lang.String)
1049     */
1050    public CmsValidationResult validateEntities(
1051        CmsEntity lastEditedEntity,
1052        String clientId,
1053        List<String> deletedEntities,
1054        Collection<String> skipPaths,
1055        String lastEditedLocale)
1056    throws CmsRpcException {
1057
1058        CmsUUID structureId = null;
1059        if (lastEditedEntity != null) {
1060            structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId());
1061        }
1062        if ((structureId == null) && !deletedEntities.isEmpty()) {
1063            structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0));
1064        }
1065        CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration(
1066            getCmsObject(),
1067            getCmsObject().getRequestContext().getRootUri());
1068        if (structureId != null) {
1069            CmsObject cms = getCmsObject();
1070            CmsResource resource = null;
1071            try {
1072                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1073                CmsFile file = cms.readFile(resource);
1074                CmsXmlContent content = getContentDocument(file, true);
1075                checkAutoCorrection(cms, content);
1076                if (lastEditedEntity != null) {
1077                    synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
1078                }
1079                for (String deleteId : deletedEntities) {
1080                    Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId));
1081                    if (content.hasLocale(contentLocale)) {
1082                        content.removeLocale(contentLocale);
1083                    }
1084                }
1085                return validateContent(cms, structureId, content);
1086
1087            } catch (Exception e) {
1088                if (resource != null) {
1089                    // TODO: What is this for?
1090                    getSessionCache().uncacheXmlContent(structureId);
1091                }
1092                error(e);
1093            }
1094        }
1095        return null;
1096    }
1097
1098    /**
1099     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#validateEntity(org.opencms.acacia.shared.CmsEntity)
1100     */
1101    public CmsValidationResult validateEntity(CmsEntity changedEntity) throws CmsRpcException {
1102
1103        CmsUUID structureId = null;
1104        if (changedEntity == null) {
1105            return new CmsValidationResult(null, null);
1106        }
1107        structureId = CmsContentDefinition.entityIdToUuid(changedEntity.getId());
1108
1109        if (structureId != null) {
1110            CmsObject cms = getCmsObject();
1111            CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
1112                cms,
1113                cms.getRequestContext().getRootUri());
1114            Set<String> setFieldNames = Sets.newHashSet();
1115            try {
1116                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1117                CmsFile file = cms.readFile(resource);
1118                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
1119                String entityId = changedEntity.getId();
1120                Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
1121                if (content.hasLocale(contentLocale)) {
1122                    content.removeLocale(contentLocale);
1123                }
1124                content.addLocale(cms, contentLocale);
1125                setFieldNames.addAll(addEntityAttributes(cms, content, "", changedEntity, contentLocale));
1126
1127                CmsValidationResult result = validateContent(cms, structureId, content, setFieldNames);
1128                CmsEntityAttribute clientIdAttr = changedEntity.getAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE);
1129                if (clientIdAttr != null) {
1130                    String clientId = clientIdAttr.getSimpleValue();
1131                    CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId);
1132                    I_CmsFormatterBean formatter = getFormatterForElement(config, containerElement);
1133                    if ((formatter != null)
1134                        && formatter.isAllowsSettingsInEditor()
1135                        && !formatter.getSettings(config).isEmpty()) {
1136                        Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings(
1137                            cms,
1138                            config,
1139                            formatter,
1140                            containerElement.getResource(),
1141                            contentLocale,
1142                            getRequest());
1143                        validateSettings(changedEntity, result, settingsConfig);
1144                    }
1145                }
1146                return result;
1147            } catch (Throwable e) {
1148                error(e);
1149            }
1150        }
1151        return new CmsValidationResult(null, null);
1152    }
1153
1154    /**
1155     * Decodes the newlink request parameter if possible.<p>
1156     *
1157     * @param newLink the parameter to decode
1158     *
1159     * @return the decoded value
1160     */
1161    protected String decodeNewLink(String newLink) {
1162
1163        String result = newLink;
1164        if (result == null) {
1165            return null;
1166        }
1167        try {
1168            result = CmsEncoder.decode(result);
1169            try {
1170                result = CmsEncoder.decode(result);
1171            } catch (Throwable e) {
1172                LOG.info(e.getLocalizedMessage(), e);
1173            }
1174        } catch (Throwable e) {
1175            LOG.info(e.getLocalizedMessage(), e);
1176        }
1177
1178        return result;
1179    }
1180
1181    /**
1182     * Returns the element name to the given element.<p>
1183     *
1184     * @param attributeName the attribute name
1185     *
1186     * @return the element name
1187     */
1188    protected String getElementName(String attributeName) {
1189
1190        if (attributeName.contains("/")) {
1191            return attributeName.substring(attributeName.lastIndexOf("/") + 1);
1192        }
1193        return attributeName;
1194    }
1195
1196    /**
1197     * Helper method to determine the encoding of the given file in the VFS,
1198     * which must be set using the "content-encoding" property.<p>
1199     *
1200     * @param cms the CmsObject
1201     * @param file the file which is to be checked
1202     * @return the encoding for the file
1203     */
1204    protected String getFileEncoding(CmsObject cms, CmsResource file) {
1205
1206        String result;
1207        try {
1208            result = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(
1209                OpenCms.getSystemInfo().getDefaultEncoding());
1210        } catch (CmsException e) {
1211            result = OpenCms.getSystemInfo().getDefaultEncoding();
1212        }
1213        return CmsEncoder.lookupEncoding(result, OpenCms.getSystemInfo().getDefaultEncoding());
1214    }
1215
1216    /**
1217     * Parses the element into an entity.<p>
1218     *
1219     * @param content the entity content
1220     * @param element the current element
1221     * @param locale the content locale
1222     * @param entityId the entity id
1223     * @param parentPath the parent path
1224     * @param typeName the entity type name
1225     * @param visitor the content type visitor
1226     * @param includeInvisible include invisible attributes
1227     * @param editedLocalEntity the edited locale entity
1228     *
1229     * @return the entity
1230     */
1231    protected CmsEntity readEntity(
1232        CmsXmlContent content,
1233        Element element,
1234        Locale locale,
1235        String entityId,
1236        String parentPath,
1237        String typeName,
1238        CmsContentTypeVisitor visitor,
1239        boolean includeInvisible,
1240        CmsEntity editedLocalEntity) {
1241
1242        String newEntityId = entityId + (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath) ? "/" + parentPath : "");
1243        CmsEntity newEntity = new CmsEntity(newEntityId, typeName);
1244        CmsEntity result = newEntity;
1245
1246        List<Element> elements = element.elements();
1247        CmsType type = visitor.getTypes().get(typeName);
1248        boolean isChoice = type.isChoice();
1249        String choiceTypeName = null;
1250        // just needed for choice attributes
1251        Map<String, Integer> attributeCounter = null;
1252        if (isChoice) {
1253            choiceTypeName = type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME);
1254            type = visitor.getTypes().get(type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME));
1255            attributeCounter = new HashMap<String, Integer>();
1256        }
1257        int counter = 0;
1258        CmsObject cms = getCmsObject();
1259        String previousName = null;
1260        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath)) {
1261            parentPath += "/";
1262        }
1263        for (Element child : elements) {
1264            String attributeName = getAttributeName(child.getName(), typeName);
1265            String subTypeName = type.getAttributeTypeName(attributeName);
1266            if (visitor.getTypes().get(subTypeName) == null) {
1267                // in case there is no type configured for this element, the schema may have changed, skip the element
1268                continue;
1269            }
1270            if (!includeInvisible && !visitor.getAttributeConfigurations().get(attributeName).isVisible()) {
1271                // skip attributes marked as invisible, there content should not be transfered to the client
1272                continue;
1273            }
1274            if (isChoice && (attributeCounter != null)) {
1275                if (!attributeName.equals(previousName)) {
1276                    if (attributeCounter.get(attributeName) != null) {
1277                        counter = attributeCounter.get(attributeName).intValue();
1278                    } else {
1279                        counter = 0;
1280                    }
1281                    previousName = attributeName;
1282                }
1283                attributeCounter.put(attributeName, Integer.valueOf(counter + 1));
1284            } else if (!attributeName.equals(previousName)) {
1285
1286                // reset the attribute counter for every attribute name
1287                counter = 0;
1288
1289                previousName = attributeName;
1290            }
1291            if (isChoice) {
1292                result = new CmsEntity(
1293                    newEntityId + "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + child.getName() + "[" + counter + "]",
1294                    choiceTypeName);
1295                newEntity.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, result);
1296            }
1297            String path = parentPath + child.getName();
1298            if (visitor.isDynamicallyLoaded(attributeName)) {
1299                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
1300                String attributeValue = getDynamicAttributeValue(
1301                    content.getFile(),
1302                    value,
1303                    attributeName,
1304                    editedLocalEntity);
1305                result.addAttributeValue(attributeName, attributeValue);
1306            } else if (visitor.getTypes().get(subTypeName).isSimpleType()) {
1307                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
1308                result.addAttributeValue(attributeName, value.getStringValue(cms));
1309            } else {
1310                CmsEntity editedSubEntity = null;
1311                if ((editedLocalEntity != null) && (editedLocalEntity.getAttribute(attributeName) != null)) {
1312                    editedSubEntity = editedLocalEntity.getAttribute(attributeName).getComplexValue();
1313                }
1314                CmsEntity subEntity = readEntity(
1315                    content,
1316                    child,
1317                    locale,
1318                    entityId,
1319                    path + "[" + (counter + 1) + "]",
1320                    subTypeName,
1321                    visitor,
1322                    includeInvisible,
1323                    editedSubEntity);
1324                result.addAttributeValue(attributeName, subEntity);
1325
1326            }
1327            counter++;
1328        }
1329        return newEntity;
1330    }
1331
1332    /**
1333     * Reads the types from the given content definition and adds the to the map of already registered
1334     * types if necessary.<p>
1335     *
1336     * @param xmlContentDefinition the XML content definition
1337     * @param locale the messages locale
1338     *
1339     * @return the types of the given content definition
1340     */
1341    protected Map<String, CmsType> readTypes(CmsXmlContentDefinition xmlContentDefinition, Locale locale) {
1342
1343        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), null, locale);
1344        visitor.visitTypes(xmlContentDefinition, locale);
1345        return visitor.getTypes();
1346    }
1347
1348    /**
1349     * Synchronizes the locale independent fields.<p>
1350     *
1351     * @param file the content file
1352     * @param content the XML content
1353     * @param skipPaths the paths to skip during locale synchronization
1354     * @param entities the edited entities
1355     * @param lastEdited the last edited locale
1356     *
1357     * @throws CmsXmlException if something goes wrong
1358     */
1359    protected void synchronizeLocaleIndependentFields(
1360        CmsFile file,
1361        CmsXmlContent content,
1362        Collection<String> skipPaths,
1363        Collection<CmsEntity> entities,
1364        Locale lastEdited)
1365    throws CmsXmlException {
1366
1367        CmsEntity lastEditedEntity = null;
1368        for (CmsEntity entity : entities) {
1369            if (lastEdited.equals(CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entity.getId())))) {
1370                lastEditedEntity = entity;
1371            } else {
1372                synchronizeLocaleIndependentForEntity(file, content, skipPaths, entity);
1373            }
1374        }
1375        if (lastEditedEntity != null) {
1376            // prepare the last edited last, to sync locale independent fields
1377            synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
1378        }
1379    }
1380
1381    /**
1382     * Transfers values marked as invisible from the original entity to the target entity.<p>
1383     *
1384     * @param original the original entity
1385     * @param target the target entiy
1386     * @param visitor the type visitor holding the content type configuration
1387     */
1388    protected void transferInvisibleValues(CmsEntity original, CmsEntity target, CmsContentTypeVisitor visitor) {
1389
1390        List<String> invisibleAttributes = new ArrayList<String>();
1391        for (Entry<String, CmsAttributeConfiguration> configEntry : visitor.getAttributeConfigurations().entrySet()) {
1392            if (!configEntry.getValue().isVisible()) {
1393                invisibleAttributes.add(configEntry.getKey());
1394            }
1395        }
1396        CmsContentDefinition.transferValues(
1397            original,
1398            target,
1399            invisibleAttributes,
1400            visitor.getTypes(),
1401            visitor.getAttributeConfigurations(),
1402            true);
1403    }
1404
1405    /**
1406    * Adds the attribute values of the entity to the given XML content.<p>
1407    *
1408    * @param cms the current cms context
1409    * @param content the XML content
1410    * @param parentPath the parent path
1411    * @param entity the entity
1412    * @param contentLocale the content locale
1413    *
1414    * @return the set of xpaths of simple fields in the XML content which were set by this method
1415    */
1416    private Set<String> addEntityAttributes(
1417        CmsObject cms,
1418        CmsXmlContent content,
1419        String parentPath,
1420        CmsEntity entity,
1421        Locale contentLocale) {
1422
1423        Set<String> fieldsSet = Sets.newHashSet();
1424        addEntityAttributes(cms, content, parentPath, entity, contentLocale, fieldsSet);
1425        return fieldsSet;
1426    }
1427
1428    /**
1429     * Adds the attribute values of the entity to the given XML content.<p>
1430     *
1431     * @param cms the current cms context
1432     * @param content the XML content
1433     * @param parentPath the parent path
1434     * @param entity the entity
1435     * @param contentLocale the content locale
1436     * @param fieldsSet set to store which fields were set in the XML content
1437     */
1438    private void addEntityAttributes(
1439        CmsObject cms,
1440        CmsXmlContent content,
1441        String parentPath,
1442        CmsEntity entity,
1443        Locale contentLocale,
1444        Set<String> fieldsSet) {
1445
1446        for (CmsEntityAttribute attribute : entity.getAttributes()) {
1447            if (!isSettingsAttribute(attribute.getAttributeName())) {
1448                if (CmsType.CHOICE_ATTRIBUTE_NAME.equals(attribute.getAttributeName())) {
1449                    List<CmsEntity> choiceEntities = attribute.getComplexValues();
1450                    for (int i = 0; i < choiceEntities.size(); i++) {
1451                        List<CmsEntityAttribute> choiceAttributes = choiceEntities.get(i).getAttributes();
1452                        // each choice entity may only have a single attribute with a single value
1453                        assert (choiceAttributes.size() == 1)
1454                            && choiceAttributes.get(
1455                                0).isSingleValue() : "each choice entity may only have a single attribute with a single value";
1456                        CmsEntityAttribute choiceAttribute = choiceAttributes.get(0);
1457                        String elementPath = parentPath + getElementName(choiceAttribute.getAttributeName());
1458                        if (choiceAttribute.isSimpleValue()) {
1459                            String value = choiceAttribute.getSimpleValue();
1460                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
1461                            if (field == null) {
1462                                field = content.addValue(cms, elementPath, contentLocale, i);
1463                            }
1464                            field.setStringValue(cms, value);
1465                            fieldsSet.add(field.getPath());
1466                        } else {
1467                            CmsEntity child = choiceAttribute.getComplexValue();
1468                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
1469                            if (field == null) {
1470                                field = content.addValue(cms, elementPath, contentLocale, i);
1471                            }
1472                            addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
1473                        }
1474                    }
1475                } else {
1476                    String elementPath = parentPath + getElementName(attribute.getAttributeName());
1477                    if (attribute.isSimpleValue()) {
1478                        List<String> values = attribute.getSimpleValues();
1479                        for (int i = 0; i < values.size(); i++) {
1480                            String value = values.get(i);
1481                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
1482                            if (field == null) {
1483                                field = content.addValue(cms, elementPath, contentLocale, i);
1484                            }
1485                            field.setStringValue(cms, value);
1486                            fieldsSet.add(field.getPath());
1487                        }
1488                    } else {
1489                        List<CmsEntity> entities = attribute.getComplexValues();
1490                        for (int i = 0; i < entities.size(); i++) {
1491                            CmsEntity child = entities.get(i);
1492                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
1493                            if (field == null) {
1494                                field = content.addValue(cms, elementPath, contentLocale, i);
1495                            }
1496                            addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
1497                        }
1498                    }
1499                }
1500            }
1501        }
1502    }
1503
1504    /**
1505     * Adds the setting attributes according to the setting configuration.<p>
1506     *
1507     * @param attributeConfiguration the attribute configuration
1508     * @param settingsConfig the setting configuration
1509     * @param nestedFormatters the nested formatters
1510     * @param messages the messages
1511     * @param contentLocale the content locale
1512     * @param settingPresets the setting presets
1513     *
1514     * @return the list of names of added attributes
1515     */
1516    private List<String> addSettingsAttributes(
1517        Map<String, CmsAttributeConfiguration> attributeConfiguration,
1518        Map<String, CmsXmlContentProperty> settingsConfig,
1519        List<I_CmsFormatterBean> nestedFormatters,
1520        CmsMessages messages,
1521        Locale contentLocale,
1522        Map<String, String> settingPresets) {
1523
1524        String attrName;
1525        List<String> attributes = new ArrayList<String>();
1526        attributeConfiguration.put(
1527            SETTINGS_CLIENT_ID_ATTRIBUTE,
1528            new CmsAttributeConfiguration(
1529                "internal_client_id",
1530                "",
1531                null,
1532                null,
1533                null,
1534                DisplayType.none.name(),
1535                false,
1536                false,
1537                false));
1538        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
1539            CmsXmlContentProperty prop = entry.getValue();
1540            String niceName = prop.getNiceName();
1541            if (CmsStringUtil.isEmptyOrWhitespaceOnly(niceName)) {
1542                niceName = prop.getName();
1543            }
1544            attrName = getSettingsAttributeName(entry.getKey());
1545            boolean visible = !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(prop.getWidget())
1546                && !settingPresets.containsKey(prop.getName());
1547            if (visible) {
1548                attributes.add(attrName);
1549            }
1550
1551            attributeConfiguration.put(
1552                attrName,
1553                new CmsAttributeConfiguration(
1554                    niceName,
1555                    prop.getDescription(),
1556                    getWidgetName(prop.getWidget()),
1557                    getWidgetConfig(prop.getWidget(), prop.getWidgetConfiguration(), messages, contentLocale),
1558                    prop.getDefault(),
1559                    DisplayType.singleline.name(),
1560                    visible,
1561                    false,
1562                    false));
1563        }
1564        if (nestedFormatters != null) {
1565            for (I_CmsFormatterBean formatter : nestedFormatters) {
1566                attrName = getSettingsAttributeName(formatter.getId());
1567                attributes.add(attrName);
1568                attributeConfiguration.put(
1569                    attrName,
1570                    new CmsAttributeConfiguration(
1571                        formatter.getNiceName(m_workplaceLocale),
1572                        "",
1573                        null,
1574                        null,
1575                        null,
1576                        DisplayType.none.name(),
1577                        true,
1578                        false,
1579                        false));
1580
1581            }
1582        }
1583        return attributes;
1584    }
1585
1586    /**
1587     * Adds the entity types according to the setting configuration.<p>
1588     *
1589     * @param entityType the entity base type name
1590     * @param types the types
1591     * @param settingsConfig the setting configuration
1592     * @param nestedFormatters the nested formatters
1593     */
1594    private void addSettingsTypes(
1595        String entityType,
1596        Map<String, CmsType> types,
1597        Map<String, CmsXmlContentProperty> settingsConfig,
1598        List<I_CmsFormatterBean> nestedFormatters) {
1599
1600        CmsType baseType = types.get(entityType);
1601        CmsType settingType = new CmsType(SETTING_TYPE_NAME);
1602        types.put(settingType.getId(), settingType);
1603        baseType.addAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE, settingType, 1, 1);
1604        // add main settings first
1605        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
1606            boolean nested = false;
1607            if (nestedFormatters != null) {
1608                for (I_CmsFormatterBean formatter : nestedFormatters) {
1609                    if (entry.getKey().startsWith(formatter.getId())) {
1610                        nested = true;
1611                        break;
1612                    }
1613                }
1614            }
1615            if (!nested) {
1616                baseType.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1);
1617            }
1618        }
1619        // add nested formatter settings after
1620        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
1621            if (nestedFormatters != null) {
1622                for (I_CmsFormatterBean formatter : nestedFormatters) {
1623                    if (entry.getKey().startsWith(formatter.getId())
1624                        && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(entry.getValue().getWidget())) {
1625                        CmsType parent = types.get(formatter.getId());
1626                        if (parent == null) {
1627                            parent = new CmsType(formatter.getId());
1628                            types.put(parent.getId(), parent);
1629                            baseType.addAttribute(getSettingsAttributeName(formatter.getId()), parent, 1, 1);
1630                        }
1631                        parent.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1);
1632                        break;
1633                    }
1634                }
1635            }
1636        }
1637    }
1638
1639    /**
1640     * Adds the settings values to the given entity.<p>
1641     *
1642     * @param entity the entity
1643     * @param containerElement the container element bean providing the values
1644     * @param nestedFormatters the nested formatters
1645     */
1646    private void addSettingsValues(
1647        CmsEntity entity,
1648        CmsContainerElementBean containerElement,
1649        List<I_CmsFormatterBean> nestedFormatters) {
1650
1651        entity.addAttributeValue(SETTINGS_CLIENT_ID_ATTRIBUTE, containerElement.editorHash());
1652        for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) {
1653            boolean nested = false;
1654            if (nestedFormatters != null) {
1655                for (I_CmsFormatterBean formatter : nestedFormatters) {
1656                    if (settingEntry.getKey().startsWith(formatter.getId())) {
1657                        String nestedSettingAttributeName = getSettingsAttributeName(formatter.getId());
1658                        CmsEntity nestedEntity = null;
1659                        CmsEntityAttribute attribute = entity.getAttribute(nestedSettingAttributeName);
1660                        if (attribute != null) {
1661                            nestedEntity = attribute.getComplexValue();
1662                        } else {
1663                            nestedEntity = new CmsEntity(nestedSettingAttributeName + "[1]", formatter.getId());
1664                            entity.addAttributeValue(nestedSettingAttributeName, nestedEntity);
1665                        }
1666                        nestedEntity.addAttributeValue(
1667                            getSettingsAttributeName(settingEntry.getKey()),
1668                            settingEntry.getValue());
1669                        nested = true;
1670                        break;
1671                    }
1672                }
1673            }
1674            if (!nested) {
1675                entity.addAttributeValue(getSettingsAttributeName(settingEntry.getKey()), settingEntry.getValue());
1676            }
1677
1678        }
1679    }
1680
1681    /**
1682     * Check if automatic content correction is required. Returns <code>true</code> if the content was changed.<p>
1683     *
1684     * @param cms the cms context
1685     * @param content the content to check
1686     *
1687     * @return <code>true</code> if the content was changed
1688     * @throws CmsXmlException if the automatic content correction failed
1689     */
1690    private boolean checkAutoCorrection(CmsObject cms, CmsXmlContent content) throws CmsXmlException {
1691
1692        boolean performedAutoCorrection = content.isTransformedVersion();
1693        try {
1694            content.validateXmlStructure(new CmsXmlEntityResolver(cms));
1695        } catch (CmsXmlException eXml) {
1696            // validation failed
1697            content.setAutoCorrectionEnabled(true);
1698            content.correctXmlStructure(cms);
1699            performedAutoCorrection = true;
1700        }
1701        return performedAutoCorrection;
1702    }
1703
1704    /**
1705     * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p>
1706     *
1707     * @param newLink A string, specifying where which new content should be created.
1708     * @param locale The locale for which the
1709     * @param referenceSitePath site path of the currently edited content.
1710     * @param modelFileSitePath site path of the model file
1711     * @param mode optional creation mode
1712     * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created.
1713     * @param editHandlerData edit handler data (will be null if no edit handler is configured or the edit handler does not handle 'new')
1714     *
1715     * @return The site-path of the newly created resource.
1716     * @throws CmsException if something goes wrong
1717     */
1718    private String createResourceToEdit(
1719        String newLink,
1720        Locale locale,
1721        String referenceSitePath,
1722        String modelFileSitePath,
1723        String mode,
1724        String postCreateHandler,
1725        CmsEditHandlerData editHandlerData)
1726    throws CmsException {
1727
1728        CmsObject cms = getCmsObject();
1729        HttpServletRequest request = getRequest();
1730        if (editHandlerData != null) {
1731            CmsContainerpageService containerpageService = new CmsContainerpageService();
1732            containerpageService.setCms(cms);
1733            containerpageService.setRequest(request);
1734            CmsResource page = cms.readResource(editHandlerData.getPageContextId(), CmsResourceFilter.ALL);
1735            CmsContainerElementBean elementBean = containerpageService.getCachedElement(
1736                editHandlerData.getClientId(),
1737                page.getRootPath());
1738            Map<String, String[]> params = CmsRequestUtil.createParameterMap(
1739                CmsEncoder.decode(editHandlerData.getRequestParams()),
1740                true,
1741                CmsEncoder.ENCODING_UTF_8);
1742            elementBean.initResource(cms);
1743            CmsResource elementResource = elementBean.getResource();
1744            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(elementResource);
1745            if (type instanceof CmsResourceTypeXmlContent) {
1746                CmsResourceTypeXmlContent xmlType = (CmsResourceTypeXmlContent)type;
1747                I_CmsEditHandler handler = xmlType.getEditHandler(cms);
1748                if (handler != null) {
1749                    return handler.handleNew(
1750                        cms,
1751                        newLink,
1752                        locale,
1753                        referenceSitePath,
1754                        modelFileSitePath,
1755                        postCreateHandler,
1756                        elementBean,
1757                        editHandlerData.getPageContextId(),
1758                        params,
1759                        editHandlerData.getOption());
1760
1761                } else {
1762                    LOG.warn(
1763                        "Invalid state: edit handler data passed in, but edit handler is undefined. type = "
1764                            + type.getTypeName());
1765                }
1766            } else {
1767                LOG.warn("Invalid state: edit handler data passed in for non-XML content type.");
1768            }
1769        }
1770        return defaultCreateResourceToEdit(
1771            cms,
1772            newLink,
1773            locale,
1774            referenceSitePath,
1775            modelFileSitePath,
1776            mode,
1777            postCreateHandler);
1778    }
1779
1780    /**
1781     * Evaluates any wildcards in the given scope and returns all allowed permutations of it.<p>
1782     *
1783     * a path like Paragraph* /Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image
1784     * in case max occurrence for Paragraph is 3
1785     *
1786     * @param scope the scope
1787     * @param definition the content definition
1788     *
1789     * @return the evaluate scope permutations
1790     */
1791    private Set<String> evaluateScope(String scope, CmsXmlContentDefinition definition) {
1792
1793        Set<String> evaluatedScopes = new HashSet<String>();
1794        if (scope.contains("*")) {
1795            // evaluate wildcards to get all allowed permutations of the scope
1796            // a path like Paragraph*/Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image
1797            // in case max occurrence for Paragraph is 3
1798
1799            String[] pathElements = scope.split("/");
1800            String parentPath = "";
1801
1802            for (int i = 0; i < pathElements.length; i++) {
1803                String elementName = pathElements[i];
1804                boolean hasWildCard = elementName.endsWith("*");
1805
1806                if (hasWildCard) {
1807                    elementName = elementName.substring(0, elementName.length() - 1);
1808                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
1809                    I_CmsXmlSchemaType type = definition.getSchemaType(parentPath);
1810                    Set<String> tempScopes = new HashSet<String>();
1811                    if (type.getMaxOccurs() == Integer.MAX_VALUE) {
1812                        throw new IllegalStateException(
1813                            "Can not use fields with unbounded maxOccurs in scopes for editor change handler.");
1814                    }
1815                    for (int j = 0; j < type.getMaxOccurs(); j++) {
1816                        if (evaluatedScopes.isEmpty()) {
1817                            tempScopes.add(elementName + "[" + (j + 1) + "]");
1818                        } else {
1819
1820                            for (String evScope : evaluatedScopes) {
1821                                tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName + "[" + (j + 1) + "]"));
1822                            }
1823                        }
1824                    }
1825                    evaluatedScopes = tempScopes;
1826                } else {
1827                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
1828                    Set<String> tempScopes = new HashSet<String>();
1829                    if (evaluatedScopes.isEmpty()) {
1830                        tempScopes.add(elementName);
1831                    } else {
1832                        for (String evScope : evaluatedScopes) {
1833                            tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName));
1834                        }
1835                    }
1836                    evaluatedScopes = tempScopes;
1837                }
1838            }
1839        } else {
1840            evaluatedScopes.add(scope);
1841        }
1842        return evaluatedScopes;
1843    }
1844
1845    /**
1846     * Evaluates the values of the locale independent fields and the paths to skip during locale synchronization.<p>
1847     *
1848     * @param content the XML content
1849     * @param syncValues the map of synchronization values
1850     * @param skipPaths the list o paths to skip
1851     */
1852    private void evaluateSyncLocaleValues(
1853        CmsXmlContent content,
1854        Map<String, String> syncValues,
1855        Collection<String> skipPaths) {
1856
1857        CmsObject cms = getCmsObject();
1858        for (Locale locale : content.getLocales()) {
1859            for (String elementPath : content.getContentDefinition().getContentHandler().getSynchronizations(
1860                true).getSynchronizationPaths()) {
1861                for (I_CmsXmlContentValue contentValue : content.getSimpleValuesBelowPath(elementPath, locale)) {
1862                    String valuePath = contentValue.getPath();
1863                    boolean skip = false;
1864                    for (String skipPath : skipPaths) {
1865                        if (valuePath.startsWith(skipPath)) {
1866                            skip = true;
1867                            break;
1868                        }
1869                    }
1870                    if (!skip) {
1871                        String value = contentValue.getStringValue(cms);
1872                        if (syncValues.containsKey(valuePath)) {
1873                            if (!syncValues.get(valuePath).equals(value)) {
1874                                // in case the current value does not match the previously stored value,
1875                                // remove it and add the parent path to the skipPaths list
1876                                syncValues.remove(valuePath);
1877                                int pathLevelDiff = (CmsResource.getPathLevel(valuePath)
1878                                    - CmsResource.getPathLevel(elementPath)) + 1;
1879                                for (int i = 0; i < pathLevelDiff; i++) {
1880                                    valuePath = CmsXmlUtils.removeLastXpathElement(valuePath);
1881                                }
1882                                skipPaths.add(valuePath);
1883                            }
1884                        } else {
1885                            syncValues.put(valuePath, value);
1886                        }
1887                    }
1888                }
1889            }
1890        }
1891    }
1892
1893    /**
1894     * Returns the change handler scopes.<p>
1895     *
1896     * @param definition the content definition
1897     *
1898     * @return the scopes
1899     */
1900    private Set<String> getChangeHandlerScopes(CmsXmlContentDefinition definition) {
1901
1902        List<I_CmsXmlContentEditorChangeHandler> changeHandlers = definition.getContentHandler().getEditorChangeHandlers(
1903            false);
1904        Set<String> scopes = new HashSet<String>();
1905        for (I_CmsXmlContentEditorChangeHandler handler : changeHandlers) {
1906            String scope = handler.getScope();
1907            scopes.addAll(evaluateScope(scope, definition));
1908        }
1909        return scopes;
1910    }
1911
1912    /**
1913     * Returns the XML content document.<p>
1914     *
1915     * @param file the resource file
1916     * @param fromCache <code>true</code> to use the cached document
1917     *
1918     * @return the content document
1919     *
1920     * @throws CmsXmlException if reading the XML fails
1921     */
1922    private CmsXmlContent getContentDocument(CmsFile file, boolean fromCache) throws CmsXmlException {
1923
1924        CmsXmlContent content = null;
1925        if (fromCache) {
1926            content = getSessionCache().getCacheXmlContent(file.getStructureId());
1927        }
1928        if (content == null) {
1929            content = CmsXmlContentFactory.unmarshal(getCmsObject(), file);
1930            getSessionCache().setCacheXmlContent(file.getStructureId(), content);
1931        }
1932        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), file, Locale.ENGLISH);
1933        visitor.visitTypes(content.getContentDefinition(), Locale.ENGLISH);
1934        CmsDynamicCategoryFieldList dynCatFields = visitor.getOptionalDynamicCategoryFields();
1935        dynCatFields.ensureFields(getCmsObject(), content);
1936        return content;
1937    }
1938
1939    /**
1940     * Returns the value that has to be set for the dynamic attribute.
1941     *
1942     * @param file the file where the current content is stored
1943     * @param value the content value that is represented by the attribute
1944     * @param attributeName the attribute's name
1945     * @param editedLocalEntity the entities that where edited last
1946     * @return the value that has to be set for the dynamic attribute.
1947     */
1948    private String getDynamicAttributeValue(
1949        CmsFile file,
1950        I_CmsXmlContentValue value,
1951        String attributeName,
1952        CmsEntity editedLocalEntity) {
1953
1954        if ((null != editedLocalEntity) && (editedLocalEntity.getAttribute(attributeName) != null)) {
1955            getSessionCache().setDynamicValue(
1956                attributeName,
1957                editedLocalEntity.getAttribute(attributeName).getSimpleValue());
1958        }
1959        String currentValue = getSessionCache().getDynamicValue(attributeName);
1960        if (null != currentValue) {
1961            return currentValue;
1962        }
1963        if (null != file) {
1964            if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
1965                List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
1966                try {
1967                    categories = CmsCategoryService.getInstance().readResourceCategories(getCmsObject(), file);
1968                } catch (CmsException e) {
1969                    LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
1970                }
1971                I_CmsWidget widget = null;
1972                widget = CmsWidgetUtil.collectWidgetInfo(getCmsObject(), value).getWidget();
1973                if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
1974                    String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory(
1975                        getCmsObject(),
1976                        getCmsObject().getSitePath(file));
1977                    StringBuffer pathes = new StringBuffer();
1978                    for (CmsCategory category : categories) {
1979                        if (category.getPath().startsWith(mainCategoryPath)) {
1980                            String path = category.getBasePath() + category.getPath();
1981                            path = getCmsObject().getRequestContext().removeSiteRoot(path);
1982                            pathes.append(path).append(',');
1983                        }
1984                    }
1985                    String dynamicConfigString = pathes.length() > 0 ? pathes.substring(0, pathes.length() - 1) : "";
1986                    getSessionCache().setDynamicValue(attributeName, dynamicConfigString);
1987                    return dynamicConfigString;
1988                }
1989            } else if (value.getTypeName().equals(CmsXmlAccessRestrictionValue.TYPE_NAME)) {
1990                CmsAccessRestrictionInfo restrictionInfo = CmsAccessRestrictionInfo.getRestrictionInfo(
1991                    getCmsObject(),
1992                    value.getDocument().getContentDefinition());
1993                if (restrictionInfo != null) {
1994                    try {
1995                        List<CmsAccessControlEntry> aces = getCmsObject().getAccessControlEntries(
1996                            getCmsObject().getSitePath(value.getDocument().getFile()));
1997                        boolean hasEntry = aces.stream().anyMatch(
1998                            ace -> ace.getPrincipal().equals(restrictionInfo.getGroup().getId())
1999                                && ace.isResponsible());
2000                        return "" + hasEntry;
2001                    } catch (CmsException e) {
2002                        LOG.error(e.getLocalizedMessage(), e);
2003                    }
2004                    return "false";
2005                }
2006            }
2007        }
2008        return "";
2009
2010    }
2011
2012    /**
2013     * Returns the formatter configuration for the given container element bean.<p>
2014     *
2015     * @param containerElement the container element
2016     *
2017     * @return the formatter configuration
2018     */
2019    private I_CmsFormatterBean getFormatterForElement(
2020        CmsADEConfigData config,
2021        CmsContainerElementBean containerElement) {
2022
2023        if ((containerElement != null)
2024            && (containerElement.getFormatterId() != null)
2025            && !containerElement.getFormatterId().isNullUUID()) {
2026            CmsUUID formatterId = containerElement.getFormatterId();
2027
2028            // find formatter config setting
2029            for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) {
2030                if (settingEntry.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) {
2031                    String formatterConfigId = settingEntry.getValue();
2032                    I_CmsFormatterBean dynamicFmt = config.findFormatter(formatterConfigId);
2033                    if ((dynamicFmt != null) && dynamicFmt.getJspStructureId().equals(formatterId)) {
2034                        return dynamicFmt;
2035                    }
2036                }
2037            }
2038        }
2039        return null;
2040    }
2041
2042    /**
2043     * Returns the path elements for the given content value.<p>
2044     *
2045     * @param content the XML content
2046     * @param value the content value
2047     *
2048     * @return the path elements
2049     */
2050    private String[] getPathElements(CmsXmlContent content, I_CmsXmlContentValue value) {
2051
2052        List<String> pathElements = new ArrayList<String>();
2053        String[] paths = value.getPath().split("/");
2054        String path = "";
2055        for (int i = 0; i < paths.length; i++) {
2056            path += paths[i];
2057            I_CmsXmlContentValue ancestor = content.getValue(path, value.getLocale());
2058            int valueIndex = ancestor.getXmlIndex();
2059            if (ancestor.isChoiceOption()) {
2060                Element parent = ancestor.getElement().getParent();
2061                valueIndex = parent.indexOf(ancestor.getElement());
2062            }
2063            String pathElement = getAttributeName(ancestor);
2064            pathElements.add(pathElement + "[" + valueIndex + "]");
2065            if (ancestor.isChoiceType()) {
2066                pathElements.add("ATTRIBUTE_CHOICE");
2067            }
2068            path += "/";
2069        }
2070        return pathElements.toArray(new String[pathElements.size()]);
2071    }
2072
2073    /**
2074     * Returns the session cache.<p>
2075     *
2076     * @return the session cache
2077     */
2078    private CmsADESessionCache getSessionCache() {
2079
2080        if (m_sessionCache == null) {
2081            m_sessionCache = CmsADESessionCache.getCache(getRequest(), getCmsObject());
2082        }
2083        return m_sessionCache;
2084    }
2085
2086    /**
2087     * Returns the attribute name to use for the given setting name.<p>
2088     *
2089     * @param settingName the setting name
2090     *
2091     * @return the attribute name
2092     */
2093    private String getSettingsAttributeName(String settingName) {
2094
2095        return "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + settingName;
2096    }
2097
2098    /**
2099     * Returns information on validation issues, sorted by locale and in the order the issues appear in the editor.
2100     * @param cms the current context
2101     * @param content the content with the issues
2102     * @param validationResult the validation result
2103     * @return information on validation issues, sorted by locale and in the order the issues appear in the editor.
2104     */
2105    private Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> getValidationIssues(
2106        CmsObject cms,
2107        CmsXmlContent content,
2108        CmsValidationResult validationResult) {
2109
2110        // only if we have warnings or errors, we can display them.
2111        if (validationResult.hasErrors() || validationResult.hasWarnings()) {
2112            // if we have errors, we display errors and warnings mixed.
2113            Map<String, Map<String[], CmsPair<String, String>>> issues = new HashMap<>();
2114            if (validationResult.hasErrors()) {
2115                validationResult.getErrors().entrySet().forEach(
2116                    e -> issues.put(CmsContentDefinition.getLocaleFromId(e.getKey()), e.getValue()));
2117            }
2118            if (validationResult.hasWarnings()) {
2119                validationResult.getWarnings().entrySet().forEach(e -> {
2120                    String locale = CmsContentDefinition.getLocaleFromId(e.getKey());
2121                    if (issues.containsKey(locale)) {
2122                        // We assume we cannot have a warning and error at the same time, so we can override here.
2123                        issues.get(locale).putAll(e.getValue());
2124                    } else {
2125                        issues.put(locale, e.getValue());
2126                    }
2127                });
2128            }
2129            // we use a tree map to sort the locales alphabetically
2130            TreeMap<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> sortedInfoPerLocale = new TreeMap<>();
2131            CmsXmlContentDefinition definition = content.getContentDefinition();
2132            for (Entry<String, Map<String[], CmsPair<String, String>>> e : issues.entrySet()) {
2133                Locale l = CmsLocaleManager.getLocale(e.getKey());
2134                // map from XML path to attributes as provided by the validation result
2135                Map<String, CmsPair<String[], String>> errorsByPath = new HashMap<>(e.getValue().size());
2136                // fill the map
2137                e.getValue().entrySet().stream().forEach(
2138                    v -> errorsByPath.put(
2139                        v.getValue().getSecond(),
2140                        new CmsPair<>(v.getKey(), v.getValue().getFirst())));
2141                // get the paths and sort them
2142                List<String> sortedPaths = new ArrayList<>(errorsByPath.keySet());
2143                sortedPaths.sort(new CmsXmlDisplayOrderPathComparator(definition));
2144                // the infos for the locale, a list of issue information, where each issue information
2145                // is a list of the path part attributes combined with the index of the path part.
2146                List<CmsPair<List<CmsPair<String, Integer>>, String>> sortedInfos = new ArrayList<>(
2147                    errorsByPath.size());
2148                // fill the info list in the correct sort order
2149                for (String p : sortedPaths) {
2150                    CmsPair<String[], String> v = errorsByPath.get(p);
2151                    String[] entities = v.getFirst();
2152                    List<CmsPair<String, Integer>> singleInfo = new ArrayList<>(entities.length);
2153                    String attributePrefix = "";
2154                    for (int i = 0; i < entities.length; i++) {
2155                        String w = entities[i];
2156                        if (!"ATTRIBUTE_CHOICE".equals(w)) {
2157                            String attr = org.opencms.acacia.shared.CmsContentDefinition.removeIndex(w);
2158                            int idx = org.opencms.acacia.shared.CmsContentDefinition.extractIndex(w);
2159                            if (!attributePrefix.isEmpty()) {
2160                                attr = attributePrefix + ":" + attr;
2161                            }
2162                            singleInfo.add(new CmsPair<>(attr, Integer.valueOf(idx + 1)));
2163                            attributePrefix += attr.substring(attr.lastIndexOf('/'));
2164                        }
2165                    }
2166                    sortedInfos.add(new CmsPair<>(singleInfo, v.getSecond()));
2167                }
2168
2169                sortedInfoPerLocale.put(l.getDisplayName(getWorkplaceLocale(cms)), sortedInfos);
2170            }
2171            return sortedInfoPerLocale;
2172        }
2173        return null;
2174    }
2175
2176    /**
2177     * Transforms the widget configuration.<p>
2178     * @param settingsWidget the settings widget name
2179     * @param settingsConfig the setting widget configuration
2180     * @param messages the workplace messages
2181     * @param contentLocale the content locale
2182     *
2183     * @return the client widget configuration string
2184     */
2185    private String getWidgetConfig(
2186        String settingsWidget,
2187        String settingsConfig,
2188        CmsMessages messages,
2189        Locale contentLocale) {
2190
2191        Class<? extends I_CmsADEWidget> widgetClass = WIDGET_MAPPINGS.get(settingsWidget);
2192        String config = "";
2193        if (widgetClass == null) {
2194            widgetClass = CmsInputWidget.class;
2195        }
2196        try {
2197            I_CmsADEWidget widget = widgetClass.newInstance();
2198            widget.setConfiguration(settingsConfig);
2199            config = widget.getConfiguration(getCmsObject(), null, messages, null, contentLocale);
2200        } catch (Exception e) {
2201            LOG.error(e.getLocalizedMessage(), e);
2202        }
2203        return config;
2204    }
2205
2206    /**
2207     * Returns the widget class name.<p>
2208     *
2209     * @param settingsWidget the settings widget name
2210     *
2211     * @return the widget class name
2212     */
2213    private String getWidgetName(String settingsWidget) {
2214
2215        if (WIDGET_MAPPINGS.containsKey(settingsWidget)) {
2216            return WIDGET_MAPPINGS.get(settingsWidget).getName();
2217        } else {
2218            return CmsInputWidget.class.getName();
2219        }
2220    }
2221
2222    /**
2223     * Returns the workplace locale.<p>
2224     *
2225     * @param cms the current OpenCms context
2226     *
2227     * @return the current users workplace locale
2228     */
2229    private Locale getWorkplaceLocale(CmsObject cms) {
2230
2231        if (m_workplaceLocale == null) {
2232            m_workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
2233        }
2234        return m_workplaceLocale;
2235    }
2236
2237    /**
2238     * Checks whether the given attribute name indicates it is a settings attribute.<p>
2239     *
2240     * @param attributeName the attribute name
2241     *
2242     * @return <code>true</code> in case of settings attributes
2243     */
2244    private boolean isSettingsAttribute(String attributeName) {
2245
2246        return attributeName.startsWith("/" + SETTINGS_ATTRIBUTE_NAME_PREFIX);
2247    }
2248
2249    private CmsADEConfigData readConfig(CmsUUID pageId) {
2250
2251        if (pageId == null) {
2252            return null;
2253        }
2254        try {
2255            CmsResource resource = getCmsObject().readResource(pageId, CmsResourceFilter.IGNORE_EXPIRATION);
2256            return OpenCms.getADEManager().lookupConfiguration(getCmsObject(), resource.getRootPath());
2257        } catch (CmsException e) {
2258            LOG.warn(e.getLocalizedMessage(), e);
2259            return null;
2260        }
2261
2262    }
2263
2264    /**
2265     * Reads the content definition for the given resource and locale.<p>
2266     *
2267     * @param file the resource file
2268     * @param content the XML content
2269     * @param entityId the entity id
2270     * @param clientId the container element client id if available
2271     * @param locale the content locale
2272     * @param newLocale if the locale content should be created as new
2273     * @param mainLocale the main language to copy in case the element language node does not exist yet
2274     * @param editedLocaleEntity the edited locale entity
2275     * @param settingPresets the presets for settings
2276     * @param configData the sitemap configuration to use
2277     *
2278     * @return the content definition
2279     *
2280     * @throws CmsException if something goes wrong
2281     */
2282    private CmsContentDefinition readContentDefinition(
2283        CmsFile file,
2284        CmsXmlContent content,
2285        String entityId,
2286        String clientId,
2287        Locale locale,
2288        boolean newLocale,
2289        Locale mainLocale,
2290        CmsEntity editedLocaleEntity,
2291        Map<String, String> settingPresets,
2292        CmsADEConfigData configData)
2293    throws CmsException {
2294
2295        long timer = 0;
2296        if (LOG.isDebugEnabled()) {
2297            timer = System.currentTimeMillis();
2298        }
2299        CmsObject cms = getCmsObject();
2300        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
2301            cms,
2302            cms.getRequestContext().getRootUri());
2303        List<Locale> availableLocalesList = OpenCms.getLocaleManager().getAvailableLocales(cms, file);
2304        if (!availableLocalesList.contains(locale)) {
2305            availableLocalesList.retainAll(content.getLocales());
2306            List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, file);
2307            Locale replacementLocale = OpenCms.getLocaleManager().getBestMatchingLocale(
2308                locale,
2309                defaultLocales,
2310                availableLocalesList);
2311            LOG.info(
2312                "Can't edit locale "
2313                    + locale
2314                    + " of file "
2315                    + file.getRootPath()
2316                    + " because it is not configured as available locale. Using locale "
2317                    + replacementLocale
2318                    + " instead.");
2319            locale = replacementLocale;
2320            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
2321        }
2322
2323        if (CmsStringUtil.isEmptyOrWhitespaceOnly(entityId)) {
2324            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
2325        }
2326        boolean performedAutoCorrection = checkAutoCorrection(cms, content);
2327        if (performedAutoCorrection) {
2328            content.initDocument();
2329        }
2330        if (LOG.isDebugEnabled()) {
2331            LOG.debug(
2332                Messages.get().getBundle().key(
2333                    Messages.LOG_TAKE_UNMARSHALING_TIME_1,
2334                    "" + (System.currentTimeMillis() - timer)));
2335        }
2336        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(cms, file, locale);
2337        if (LOG.isDebugEnabled()) {
2338            timer = System.currentTimeMillis();
2339        }
2340        visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
2341        if (LOG.isDebugEnabled()) {
2342            LOG.debug(
2343                Messages.get().getBundle().key(
2344                    Messages.LOG_TAKE_VISITING_TYPES_TIME_1,
2345                    "" + (System.currentTimeMillis() - timer)));
2346        }
2347        CmsEntity entity = null;
2348        Map<String, String> syncValues = new HashMap<String, String>();
2349        Collection<String> skipPaths = new HashSet<String>();
2350        evaluateSyncLocaleValues(content, syncValues, skipPaths);
2351        if (content.hasLocale(locale) && newLocale) {
2352            // a new locale is requested, so remove the present one
2353            content.removeLocale(locale);
2354        }
2355        if (!content.hasLocale(locale)) {
2356            if ((mainLocale != null) && content.hasLocale(mainLocale)) {
2357                content.copyLocale(mainLocale, locale);
2358            } else {
2359                content.addLocale(cms, locale);
2360            }
2361            // sync the locale values
2362            if (!visitor.getLocaleSynchronizations().isEmpty() && (content.getLocales().size() > 1)) {
2363                for (Locale contentLocale : content.getLocales()) {
2364                    if (!contentLocale.equals(locale)) {
2365                        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
2366                    }
2367                }
2368            }
2369        }
2370        visitor.getOptionalDynamicCategoryFields().ensureFields(cms, content, locale);
2371        Element element = content.getLocaleNode(locale);
2372        if (LOG.isDebugEnabled()) {
2373            timer = System.currentTimeMillis();
2374        }
2375        entity = readEntity(
2376            content,
2377            element,
2378            locale,
2379            entityId,
2380            "",
2381            getTypeUri(content.getContentDefinition()),
2382            visitor,
2383            false,
2384            editedLocaleEntity);
2385
2386        if (LOG.isDebugEnabled()) {
2387            LOG.debug(
2388                Messages.get().getBundle().key(
2389                    Messages.LOG_TAKE_READING_ENTITY_TIME_1,
2390                    "" + (System.currentTimeMillis() - timer)));
2391        }
2392        List<String> contentLocales = new ArrayList<String>();
2393        for (Locale contentLocale : content.getLocales()) {
2394            contentLocales.add(contentLocale.toString());
2395        }
2396        Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
2397        LinkedHashMap<String, String> availableLocales = new LinkedHashMap<String, String>();
2398        for (Locale availableLocale : OpenCms.getLocaleManager().getAvailableLocales(cms, file)) {
2399            availableLocales.put(availableLocale.toString(), availableLocale.getDisplayName(workplaceLocale));
2400        }
2401        String title = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue();
2402        try {
2403            CmsGallerySearchResult searchResult = CmsGallerySearch.searchById(cms, file.getStructureId(), locale);
2404            title = searchResult.getTitle();
2405        } catch (CmsException e) {
2406            LOG.warn(e.getLocalizedMessage(), e);
2407        }
2408        String typeName = OpenCms.getResourceManager().getResourceType(file.getTypeId()).getTypeName();
2409        boolean autoUnlock = OpenCms.getWorkplaceManager().shouldAcaciaUnlock();
2410        Map<String, CmsEntity> entities = new HashMap<String, CmsEntity>();
2411        entities.put(entityId, entity);
2412
2413        Map<String, CmsAttributeConfiguration> attrConfig = visitor.getAttributeConfigurations();
2414        Map<String, CmsType> types = visitor.getTypes();
2415        List<CmsTabInfo> tabInfos = visitor.getTabInfos();
2416
2417        if (clientId != null) {
2418            CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId);
2419            I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement);
2420            if ((formatter != null)
2421                && formatter.isAllowsSettingsInEditor()
2422                && !formatter.getSettings(config).isEmpty()) {
2423                Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings(
2424                    cms,
2425                    config,
2426                    formatter,
2427                    containerElement.getResource(),
2428                    locale,
2429                    getRequest());
2430                com.google.common.base.Supplier<CmsXmlContent> contentSupplier = Suppliers.memoize(() -> {
2431                    try {
2432                        return CmsXmlContentFactory.unmarshal(cms, cms.readFile(containerElement.getResource()));
2433                    } catch (CmsException e) {
2434                        LOG.error(e.getLocalizedMessage(), e);
2435                        return null;
2436                    }
2437                });
2438                settingsConfig = CmsXmlContentPropertyHelper.resolveMacrosForPropertyInfo(
2439                    cms,
2440                    null,
2441                    containerElement.getResource(),
2442                    contentSupplier,
2443                    CmsElementUtil.createStringTemplateSource(formatter, contentSupplier),
2444                    settingsConfig);
2445                CmsMessages messages = OpenCms.getWorkplaceManager().getMessages(m_workplaceLocale);
2446                List<I_CmsFormatterBean> nestedFormatters = formatter.hasNestedFormatterSettings()
2447                ? OpenCms.getADEManager().getNestedFormatters(
2448                    cms,
2449                    config,
2450                    containerElement.getResource(),
2451                    locale,
2452                    getRequest())
2453                : Collections.emptyList();
2454                String firstContentAttributeName = types.get(
2455                    entity.getTypeName()).getAttributeNames().iterator().next();
2456                List<String> addedVisibleAttrs = addSettingsAttributes(
2457                    attrConfig,
2458                    settingsConfig,
2459                    nestedFormatters,
2460                    messages,
2461                    locale,
2462                    settingPresets);
2463                addSettingsTypes(entity.getTypeName(), types, settingsConfig, nestedFormatters);
2464                if (editedLocaleEntity != null) {
2465                    transferSettingValues(editedLocaleEntity, entity);
2466                } else {
2467                    addSettingsValues(entity, containerElement, nestedFormatters);
2468                }
2469                if (tabInfos.isEmpty()) {
2470                    tabInfos.add(
2471                        new CmsTabInfo(
2472                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_CONTENT_TAB_LABEL_0),
2473                            Messages.GUI_CONTENT_TAB_LABEL_0,
2474                            Messages.GUI_CONTENT_TAB_LABEL_0,
2475                            "content",
2476                            firstContentAttributeName.substring(entity.getTypeName().length() + 1),
2477                            false,
2478                            null,
2479                            null,
2480                            null));
2481                }
2482                if (addedVisibleAttrs.size() > 0) {
2483                    tabInfos.add(
2484                        new CmsTabInfo(
2485                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_LABEL_0),
2486                            Messages.GUI_SETTINGS_TAB_LABEL_0,
2487                            Messages.GUI_SETTINGS_TAB_LABEL_0,
2488                            CmsContentDefinition.SETTINGS_TAB_ID,
2489                            CmsFileUtil.removeLeadingSeparator(addedVisibleAttrs.iterator().next()),
2490                            false,
2491                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_DESCRIPTION_0),
2492                            null,
2493                            null));
2494                }
2495            }
2496
2497        }
2498
2499        return new CmsContentDefinition(
2500            entityId,
2501            entities,
2502            visitor.getAttributeConfigurations(),
2503            visitor.getWidgetConfigurations(),
2504            visitor.getComplexWidgetData(),
2505            visitor.getTypes(),
2506            visitor.getTabInfos(),
2507            locale.toString(),
2508            contentLocales,
2509            availableLocales,
2510            visitor.getLocaleSynchronizations(),
2511            syncValues,
2512            skipPaths,
2513            title,
2514            cms.getSitePath(file),
2515            typeName,
2516            CmsIconUtil.getIconClasses(CmsIconUtil.getDisplayType(cms, file), file.getName(), false),
2517            performedAutoCorrection,
2518            autoUnlock,
2519            getChangeHandlerScopes(content.getContentDefinition()));
2520    }
2521
2522    /**
2523     * Creates a new resource according to the new link, or returns the model file informations
2524     * modelFileId is <code>null</code> but required.<p>
2525     *
2526     * @param newLink the new link
2527     * @param referenceResource the reference resource
2528     * @param modelFileId the model file structure id
2529     * @param locale the content locale
2530     * @param mode the content creation mode
2531     * @param postCreateHandler the class name for the post-create handler
2532     * @param editHandlerData the edit handler data, in case the 'new' function is handled by an edit handler
2533     * @param settingPresets the presets for settings
2534     *
2535     * @return the content definition
2536     *
2537     * @throws CmsException if creating the resource failed
2538     */
2539    private CmsContentDefinition readContentDefinitionForNew(
2540        String newLink,
2541        CmsResource referenceResource,
2542        CmsUUID modelFileId,
2543        Locale locale,
2544        String mode,
2545        String postCreateHandler,
2546        CmsEditHandlerData editHandlerData,
2547        Map<String, String> settingPresets)
2548    throws CmsException {
2549
2550        String sitePath = getCmsObject().getSitePath(referenceResource);
2551        String resourceType;
2552        if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) {
2553            resourceType = CmsJspTagEdit.getTypeFromNewLink(newLink);
2554        } else {
2555            resourceType = OpenCms.getResourceManager().getResourceType(referenceResource.getTypeId()).getTypeName();
2556        }
2557        String modelFile = null;
2558        if (modelFileId == null) {
2559            List<CmsResource> modelResources = CmsResourceTypeXmlContent.getModelFiles(
2560                getCmsObject(),
2561                CmsResource.getFolderPath(sitePath),
2562                resourceType);
2563            if (!modelResources.isEmpty()) {
2564                List<CmsModelResourceInfo> modelInfos = CmsContainerpageService.generateModelResourceList(
2565                    getCmsObject(),
2566                    resourceType,
2567                    modelResources,
2568                    locale);
2569                return new CmsContentDefinition(
2570                    modelInfos,
2571                    newLink,
2572                    referenceResource.getStructureId(),
2573                    locale.toString());
2574            }
2575        } else if (!modelFileId.isNullUUID()) {
2576            modelFile = getCmsObject().getSitePath(
2577                getCmsObject().readResource(modelFileId, CmsResourceFilter.IGNORE_EXPIRATION));
2578        }
2579        String newFileName = createResourceToEdit(
2580            newLink,
2581            locale,
2582            sitePath,
2583            modelFile,
2584            mode,
2585            postCreateHandler,
2586            editHandlerData);
2587        CmsResource resource = getCmsObject().readResource(newFileName, CmsResourceFilter.IGNORE_EXPIRATION);
2588        CmsFile file = getCmsObject().readFile(resource);
2589        CmsXmlContent content = getContentDocument(file, false);
2590        CmsContentDefinition contentDefinition = readContentDefinition(
2591            file,
2592            content,
2593            null,
2594            null,
2595            locale,
2596            false,
2597            null,
2598            null,
2599            settingPresets,
2600            null);
2601        contentDefinition.setDeleteOnCancel(true);
2602        return contentDefinition;
2603    }
2604
2605    /**
2606     * Stores the settings attributes to the container element bean persisted in the session cache.<p>
2607     * The container page editor will write the container page XML.<p>
2608     *
2609     * @param entity the entity
2610     * @param containerElement the container element
2611     * @param settingsConfig the settings configuration
2612     * @param nestedFormatters the nested formatters
2613     *
2614     * @return <code>true</code> in case any changed settings where saved
2615     */
2616    @SuppressWarnings("null")
2617    private boolean saveSettings(
2618        CmsEntity entity,
2619        CmsContainerElementBean containerElement,
2620        Map<String, CmsXmlContentProperty> settingsConfig,
2621        List<I_CmsFormatterBean> nestedFormatters) {
2622
2623        boolean hasChangedSettings = false;
2624        Map<String, String> values = new HashMap<>(containerElement.getIndividualSettings());
2625        for (Entry<String, CmsXmlContentProperty> settingsEntry : settingsConfig.entrySet()) {
2626            String value = null;
2627            boolean nested = false;
2628            if (nestedFormatters != null) {
2629                for (I_CmsFormatterBean formatter : nestedFormatters) {
2630                    if (settingsEntry.getKey().startsWith(formatter.getId())) {
2631
2632                        CmsEntity nestedEntity = null;
2633                        CmsEntityAttribute attribute = entity.getAttribute(getSettingsAttributeName(formatter.getId()));
2634                        if (attribute != null) {
2635                            nestedEntity = attribute.getComplexValue();
2636                            CmsEntityAttribute valueAttribute = nestedEntity.getAttribute(
2637                                getSettingsAttributeName(settingsEntry.getKey()));
2638                            if (valueAttribute != null) {
2639                                value = valueAttribute.getSimpleValue();
2640                            }
2641                        }
2642                        nested = true;
2643                        break;
2644                    }
2645                }
2646            }
2647            if (!nested) {
2648                CmsEntityAttribute valueAttribute = entity.getAttribute(
2649                    getSettingsAttributeName(settingsEntry.getKey()));
2650                if (valueAttribute != null) {
2651                    value = valueAttribute.getSimpleValue();
2652                }
2653            }
2654            if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)
2655                && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget())
2656                && values.containsKey(settingsEntry.getKey())) {
2657                values.remove(settingsEntry.getKey());
2658                hasChangedSettings = true;
2659            } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)
2660                && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget())
2661                && !value.equals(values.get(settingsEntry.getKey()))) {
2662                values.put(settingsEntry.getKey(), value);
2663                hasChangedSettings = true;
2664            }
2665        }
2666        if (hasChangedSettings) {
2667            containerElement.updateIndividualSettings(values);
2668            getSessionCache().setCacheContainerElement(containerElement.editorHash(), containerElement);
2669        }
2670        return hasChangedSettings;
2671    }
2672
2673    /**
2674     * Synchronizes the locale independent fields for the given entity.<p>
2675     *
2676     * @param file the content file
2677     * @param content the XML content
2678     * @param skipPaths the paths to skip during locale synchronization
2679     * @param entity the entity
2680     *
2681     * @throws CmsXmlException if something goes wrong
2682     */
2683    private void synchronizeLocaleIndependentForEntity(
2684        CmsFile file,
2685        CmsXmlContent content,
2686        Collection<String> skipPaths,
2687        CmsEntity entity)
2688    throws CmsXmlException {
2689
2690        CmsObject cms = getCmsObject();
2691        String entityId = entity.getId();
2692        Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
2693        CmsContentTypeVisitor visitor = null;
2694        CmsEntity originalEntity = null;
2695        if (content.getHandler().hasVisibilityHandlers()) {
2696            visitor = new CmsContentTypeVisitor(cms, file, contentLocale);
2697            visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
2698        }
2699        if (content.hasLocale(contentLocale)) {
2700            if ((visitor != null) && visitor.hasInvisibleFields()) {
2701                // we need to add invisible content values to the entity before saving
2702                Element element = content.getLocaleNode(contentLocale);
2703                originalEntity = readEntity(
2704                    content,
2705                    element,
2706                    contentLocale,
2707                    entityId,
2708                    "",
2709                    getTypeUri(content.getContentDefinition()),
2710                    visitor,
2711                    true,
2712                    entity);
2713            }
2714            content.removeLocale(contentLocale);
2715        }
2716        content.addLocale(cms, contentLocale);
2717        if ((visitor != null) && visitor.hasInvisibleFields()) {
2718            transferInvisibleValues(originalEntity, entity, visitor);
2719        }
2720        addEntityAttributes(cms, content, "", entity, contentLocale);
2721        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
2722    }
2723
2724    /**
2725     * Transfers settings attribute values from one entity to another.<p>
2726     *
2727     * @param source the source entity
2728     * @param target the target entity
2729     */
2730    private void transferSettingValues(CmsEntity source, CmsEntity target) {
2731
2732        for (CmsEntityAttribute attr : source.getAttributes()) {
2733            if (isSettingsAttribute(attr.getAttributeName())) {
2734                if (attr.isSimpleValue()) {
2735                    target.addAttributeValue(attr.getAttributeName(), attr.getSimpleValue());
2736                } else {
2737                    CmsEntity nestedSource = attr.getComplexValue();
2738
2739                    CmsEntity nested = new CmsEntity(nestedSource.getId(), nestedSource.getTypeName());
2740                    for (CmsEntityAttribute nestedAttr : nestedSource.getAttributes()) {
2741                        nested.addAttributeValue(nestedAttr.getAttributeName(), nestedAttr.getSimpleValue());
2742                    }
2743                    target.addAttributeValue(attr.getAttributeName(), nested);
2744                }
2745            }
2746        }
2747    }
2748
2749    /**
2750     * Validates the given XML content.<p>
2751     *
2752     * @param cms the cms context
2753     * @param structureId the structure id
2754     * @param content the XML content
2755     *
2756     * @return the validation result
2757     */
2758    private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content) {
2759
2760        return validateContent(cms, structureId, content, null);
2761    }
2762
2763    /**
2764     * Validates the given XML content.<p>
2765     *
2766     * @param cms the cms context
2767     * @param structureId the structure id
2768     * @param content the XML content
2769     * @param fieldNames if not null, only validation errors in paths from this set will be added to the validation result
2770     *
2771     * @return the validation result
2772     */
2773    private CmsValidationResult validateContent(
2774        CmsObject cms,
2775        CmsUUID structureId,
2776        CmsXmlContent content,
2777        Set<String> fieldNames) {
2778
2779        CmsXmlContentErrorHandler errorHandler = content.validate(cms);
2780        Map<String, Map<String[], CmsPair<String, String>>> errorsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>();
2781
2782        if (errorHandler.hasErrors()) {
2783            boolean reallyHasErrors = false;
2784            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getErrors().entrySet()) {
2785                Map<String[], CmsPair<String, String>> errors = new HashMap<String[], CmsPair<String, String>>();
2786                for (Entry<String, String> error : localeEntry.getValue().entrySet()) {
2787                    I_CmsXmlContentValue value = content.getValue(error.getKey(), localeEntry.getKey());
2788                    if (content.getHandler().isVisible(
2789                        cms,
2790                        value,
2791                        CmsXmlUtils.removeAllXpathIndices(value.getPath()),
2792                        content.getFile(),
2793                        localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) {
2794                        errors.put(
2795                            getPathElements(content, value),
2796                            new CmsPair<String, String>(error.getValue(), error.getKey()));
2797                        reallyHasErrors = true;
2798                    }
2799
2800                }
2801                if (reallyHasErrors) {
2802                    errorsByEntity.put(
2803                        CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
2804                        errors);
2805                }
2806            }
2807        }
2808        Map<String, Map<String[], CmsPair<String, String>>> warningsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>();
2809        if (errorHandler.hasWarnings()) {
2810            boolean reallyHasErrors = false;
2811            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getWarnings().entrySet()) {
2812                Map<String[], CmsPair<String, String>> warnings = new HashMap<String[], CmsPair<String, String>>();
2813                for (Entry<String, String> warning : localeEntry.getValue().entrySet()) {
2814                    I_CmsXmlContentValue value = content.getValue(warning.getKey(), localeEntry.getKey());
2815                    if (content.getHandler().isVisible(
2816                        cms,
2817                        value,
2818                        CmsXmlUtils.removeAllXpathIndices(value.getPath()),
2819                        content.getFile(),
2820                        localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) {
2821                        warnings.put(
2822                            getPathElements(content, value),
2823                            new CmsPair<String, String>(warning.getValue(), warning.getKey()));
2824                        reallyHasErrors = true;
2825                    }
2826                }
2827                if (reallyHasErrors) {
2828                    warningsByEntity.put(
2829                        CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
2830                        warnings);
2831                }
2832            }
2833        }
2834        return new CmsValidationResult(errorsByEntity, warningsByEntity);
2835    }
2836
2837    /**
2838     * Validates the settings attribute values.<p>
2839     *
2840     * @param entity the entity to validate
2841     * @param validationResult the validation result
2842     * @param settingsConfig the settings configuration
2843     */
2844    private void validateSettings(
2845        CmsEntity entity,
2846        CmsValidationResult validationResult,
2847        Map<String, CmsXmlContentProperty> settingsConfig) {
2848
2849        Map<String, Map<String[], CmsPair<String, String>>> errors = validationResult.getErrors();
2850        Map<String[], CmsPair<String, String>> entityErrors = errors.get(entity.getId());
2851        if (entityErrors == null) {
2852            entityErrors = new HashMap<String[], CmsPair<String, String>>();
2853        }
2854        Map<String, Map<String[], CmsPair<String, String>>> warnings = validationResult.getWarnings();
2855        Map<String[], CmsPair<String, String>> entityWarnings = warnings.get(entity.getId());
2856        if (entityWarnings == null) {
2857            entityWarnings = new HashMap<String[], CmsPair<String, String>>();
2858        }
2859
2860        for (CmsEntityAttribute attribute : entity.getAttributes()) {
2861            if (isSettingsAttribute(attribute.getAttributeName())) {
2862                if (attribute.isSimpleValue()) {
2863                    String settingsKey = attribute.getAttributeName().substring(
2864                        SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1);
2865                    CmsXmlContentProperty prop = settingsConfig.get(settingsKey);
2866                    if (prop != null) {
2867                        String regex = prop.getRuleRegex();
2868                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) {
2869                            if (!attribute.getSimpleValue().matches(regex)) {
2870                                String[] path = new String[] {attribute.getAttributeName()};
2871
2872                                if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) {
2873                                    entityErrors.put(
2874                                        path,
2875                                        new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
2876                                } else {
2877                                    entityWarnings.put(
2878                                        path,
2879                                        new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
2880                                }
2881                            }
2882                        }
2883                    }
2884                } else {
2885                    CmsEntity nested = attribute.getComplexValue();
2886                    for (CmsEntityAttribute nestedAttribute : nested.getAttributes()) {
2887                        String settingsKey = nestedAttribute.getAttributeName().substring(
2888                            SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1);
2889                        CmsXmlContentProperty prop = settingsConfig.get(settingsKey);
2890                        if (prop != null) {
2891                            String regex = prop.getRuleRegex();
2892                            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) {
2893                                if (!nestedAttribute.getSimpleValue().matches(regex)) {
2894                                    String[] path = new String[] {
2895                                        attribute.getAttributeName(),
2896                                        nestedAttribute.getAttributeName()};
2897                                    if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) {
2898                                        entityErrors.put(
2899                                            path,
2900                                            new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
2901                                    } else {
2902                                        entityWarnings.put(
2903                                            path,
2904                                            new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
2905                                    }
2906                                }
2907                            }
2908                        }
2909                    }
2910                }
2911            }
2912        }
2913        if (!entityErrors.isEmpty()) {
2914            errors.put(entity.getId(), entityErrors);
2915        }
2916        if (!entityWarnings.isEmpty()) {
2917            warnings.put(entity.getId(), entityWarnings);
2918        }
2919
2920    }
2921
2922    /**
2923     * Writes the categories that are dynamically read/wrote by the content editor.
2924     *
2925     * @param file the file where the content is stored.
2926     * @param content the content.
2927     * @param lastEditedEntity the last edited entity
2928     */
2929    private void writeCategories(CmsFile file, CmsXmlContent content, CmsEntity lastEditedEntity) {
2930
2931        // do nothing if one of the arguments is empty.
2932        if ((null == content) || (null == file)) {
2933            return;
2934        }
2935
2936        CmsObject cms = getCmsObject();
2937        if (!content.getLocales().isEmpty()) {
2938            Locale locale = content.getLocales().iterator().next();
2939            CmsEntity entity = lastEditedEntity;
2940            List<I_CmsXmlContentValue> values = content.getValues(locale);
2941            for (I_CmsXmlContentValue value : values) {
2942                if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
2943                    I_CmsWidget widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget();
2944                    List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
2945                    try {
2946                        categories = CmsCategoryService.getInstance().readResourceCategories(cms, file);
2947                    } catch (CmsException e) {
2948                        LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
2949                    }
2950                    if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
2951                        String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory(
2952                            cms,
2953                            cms.getSitePath(file));
2954                        for (CmsCategory category : categories) {
2955                            if (category.getPath().startsWith(mainCategoryPath)) {
2956                                try {
2957                                    CmsCategoryService.getInstance().removeResourceFromCategory(
2958                                        cms,
2959                                        cms.getSitePath(file),
2960                                        category);
2961                                } catch (CmsException e) {
2962                                    LOG.error(e.getLocalizedMessage(), e);
2963                                }
2964                            }
2965                        }
2966                        if (null == entity) {
2967                            try {
2968                                CmsContentDefinition definition = readContentDefinition(
2969                                    file,
2970                                    content,
2971                                    "dummy",
2972                                    null,
2973                                    locale,
2974                                    false,
2975                                    null,
2976                                    null,
2977                                    Collections.emptyMap(),
2978                                    null);
2979                                entity = definition.getEntity();
2980                            } catch (CmsException e) {
2981                                LOG.error(e.getLocalizedMessage(), e);
2982                            }
2983                        }
2984                        String checkedCategories = "";
2985                        if (null != entity) {
2986                            checkedCategories = CmsEntity.getValueForPath(entity, value.getPath().split("/"));
2987                        }
2988                        List<String> checkedCategoryList = Arrays.asList(checkedCategories.split(","));
2989                        for (String category : checkedCategoryList) {
2990                            try {
2991                                CmsCategoryService.getInstance().addResourceToCategory(
2992                                    cms,
2993                                    cms.getSitePath(file),
2994                                    CmsCategoryService.getInstance().getCategory(cms, category));
2995                            } catch (CmsException e) {
2996                                if (LOG.isWarnEnabled()) {
2997                                    LOG.warn(e.getLocalizedMessage(), e);
2998                                }
2999                            }
3000                        }
3001                    }
3002                }
3003            }
3004        }
3005    }
3006
3007    /**
3008     * Writes the xml content to the vfs and re-initializes the member variables.<p>
3009     *
3010     * @param cms the cms context
3011     * @param file the file to write to
3012     * @param content the content
3013     * @param encoding the file encoding
3014     *
3015     * @return the content
3016     *
3017     * @throws CmsException if writing the file fails
3018     */
3019    private CmsXmlContent writeContent(CmsObject cms, CmsFile file, CmsXmlContent content, String encoding)
3020    throws CmsException {
3021
3022        String decodedContent = content.toString();
3023        try {
3024            file.setContents(decodedContent.getBytes(encoding));
3025        } catch (UnsupportedEncodingException e) {
3026            throw new CmsException(
3027                org.opencms.workplace.editors.Messages.get().container(
3028                    org.opencms.workplace.editors.Messages.ERR_INVALID_CONTENT_ENC_1,
3029                    file.getRootPath()),
3030                e);
3031        }
3032        // the file content might have been modified during the write operation
3033
3034        cms.getRequestContext().setAttribute(ATTR_EDITOR_SAVING, "true");
3035        try {
3036            file = cms.writeFile(file);
3037        } finally {
3038            cms.getRequestContext().removeAttribute(ATTR_EDITOR_SAVING);
3039        }
3040        return CmsXmlContentFactory.unmarshal(cms, file);
3041    }
3042
3043}