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