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