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