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