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