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