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 org.apache.commons.logging.Log;
148
149import org.dom4j.Element;
150
151import com.google.common.base.Suppliers;
152import com.google.common.cache.Cache;
153import com.google.common.cache.CacheBuilder;
154import com.google.common.collect.Sets;
155
156import jakarta.servlet.http.HttpServletRequest;
157import jakarta.servlet.http.HttpSession;
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        /** The content augmentation used. */
170        private I_CmsXmlContentAugmentation m_augmentation;
171
172        /** The context supplied to the content augmentation job. */
173        private I_CmsXmlContentAugmentation.Context m_context;
174
175        private volatile Throwable m_exception;
176
177        /** The job id. */
178        private CmsUUID m_id = new CmsUUID();
179
180        /** The message to display. */
181        private volatile String m_message;
182
183        /** The locale to switch to. */
184        private volatile Locale m_nextLocale;
185
186        /**
187         * The original copy of the XML content object.
188         */
189        private CmsXmlContent m_originalCopy;
190
191        /** The progress message to display. */
192        private volatile String m_progress;
193
194        /** The results of the content augmentation. */
195        private volatile CmsXmlContent m_result;
196
197        /** The job state. */
198        private volatile AugmentationJobState m_state = AugmentationJobState.running;
199
200        /** The structure id of the content being augmented. */
201        private CmsUUID m_structureId;
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        aborted, done, running;
401    }
402
403    public static final String ATTR_AUGMENTATION_JOBS = "augmentationJobs";
404
405    /** Request context attribute to mark a writeFile() triggered by the user saving in the content editor. */
406    public static final String ATTR_EDITOR_SAVING = "__EDITOR_SAVING";
407
408    /** The logger for this class. */
409    protected static final Log LOG = CmsLog.getLog(CmsContentService.class);
410
411    /** The type name prefix. */
412    static final String TYPE_NAME_PREFIX = "http://opencms.org/types/";
413
414    private static ExecutorService m_augmentationThreadPool = Executors.newCachedThreadPool();
415
416    /** The RDFA attributes string. */
417    private static final String RDFA_ATTRIBUTES = CmsGwtConstants.ATTR_DATA_ID
418        + "=\"%1$s\" "
419        + CmsGwtConstants.ATTR_DATA_FIELD
420        + "=\"%2$s\"";
421
422    /** The serial version id. */
423    private static final long serialVersionUID = 7873052619331296648L;
424
425    /** The setting type name. */
426    private static final String SETTING_TYPE_NAME = "###SETTING_TYPE###";
427
428    /** The settings attribute name prefix. */
429    private static final String SETTINGS_ATTRIBUTE_NAME_PREFIX = "SETTING:::";
430
431    /** The attribute name used for the client id. */
432    private static final String SETTINGS_CLIENT_ID_ATTRIBUTE = "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + "CLIENT_ID:::";
433
434    /** The settings validation rule type name. */
435    private static final String SETTINGS_RULE_TYPE_ERROR = "error";
436
437    /** Mapping client widget names to server side widget classes. */
438    private static final Map<String, Class<? extends I_CmsADEWidget>> WIDGET_MAPPINGS = new HashMap<>();
439
440    static {
441        WIDGET_MAPPINGS.put("string", CmsInputWidget.class);
442        WIDGET_MAPPINGS.put("select", CmsSelectWidget.class);
443        WIDGET_MAPPINGS.put("multicheck", org.opencms.widgets.CmsMultiSelectWidget.class);
444        WIDGET_MAPPINGS.put("selectcombo", CmsSelectComboWidget.class);
445        WIDGET_MAPPINGS.put("checkbox", CmsCheckboxWidget.class);
446        WIDGET_MAPPINGS.put("combo", CmsComboWidget.class);
447        WIDGET_MAPPINGS.put("datebox", CmsCalendarWidget.class);
448        WIDGET_MAPPINGS.put("gallery", CmsVfsFileWidget.class);
449        WIDGET_MAPPINGS.put("multiselectbox", CmsMultiSelectWidget.class);
450        WIDGET_MAPPINGS.put("radio", CmsRadioSelectWidget.class);
451        WIDGET_MAPPINGS.put("groupselection", CmsGroupWidget.class);
452    }
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     * Synchronizes and transforms the edited content.<p>
1354     *
1355     * @param entityId the entity id
1356     * @param clientId the client id
1357     * @param editedEntity the edited entity
1358     * @param deletedEntities the deleted entities
1359     * @param skipPaths the paths to skip
1360     *
1361     * @return the content augmentation details
1362     *
1363     * @throws CmsRpcException if something goes wrong
1364     */
1365    public CmsContentAugmentationDetails synchronizeAndTransform(
1366        String entityId,
1367        String clientId,
1368        CmsEntity editedEntity,
1369        List<String> deletedEntities,
1370        Collection<String> skipPaths)
1371    throws CmsRpcException {
1372
1373        CmsObject cms = getCmsObject();
1374        try {
1375            CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entityId);
1376            CmsResource resource = getCmsObject().readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1377            CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
1378                cms,
1379                cms.getRequestContext().getRootUri());
1380            CmsFile file = cms.readFile(resource);
1381            CmsXmlContent content = getContentDocument(file, true);
1382            if (editedEntity != null) {
1383                synchronizeLocaleIndependentForEntity(file, content, skipPaths, editedEntity);
1384            }
1385            for (String deleteId : deletedEntities) {
1386                Locale localeToDelete = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId));
1387                if (content.hasLocale(localeToDelete)) {
1388                    content.removeLocale(localeToDelete);
1389                }
1390            }
1391
1392            byte[] contentData = content.marshal();
1393            CmsFile fileCopy = new CmsFile(content.getFile());
1394            fileCopy.setContents(contentData);
1395
1396            I_CmsXmlContentAugmentation augmentation = getContentAugmentation(cms, resource, config);
1397            Locale currentLocale = CmsLocaleManager.getLocale(
1398                CmsContentDefinition.getLocaleFromId(editedEntity.getId()));
1399            CmsXmlContent originalCopy = CmsXmlContentFactory.unmarshal(cms, fileCopy);
1400            CmsXmlContent[] resultContainer = {content}; // if not set, keeps the original
1401            Locale[] nextLocaleContainer = {null};
1402            StringBuilder messageBuffer = new StringBuilder();
1403            if (augmentation != null) {
1404
1405                augmentation.augmentContent(new I_CmsXmlContentAugmentation.Context() {
1406
1407                    private boolean m_callingJsp;
1408
1409                    @Override
1410                    public void callJsp(String path) throws Exception {
1411
1412                        if (m_callingJsp) {
1413                            throw new IllegalStateException("callJsp method can not be used reentrantly");
1414                        }
1415                        m_callingJsp = true;
1416                        try {
1417                            getRequest().setAttribute(I_CmsXmlContentAugmentation.ATTR_CONTEXT, this);
1418                            CmsResource resource = cms.readResource(path);
1419                            OpenCms.getResourceManager().getLoader(resource).dump(
1420                                cms,
1421                                resource,
1422                                null,
1423                                cms.getRequestContext().getLocale(),
1424                                getRequest(),
1425                                getResponse());
1426                        } finally {
1427                            getRequest().removeAttribute(I_CmsXmlContentAugmentation.ATTR_CONTEXT);
1428                            m_callingJsp = false;
1429                        }
1430                    }
1431
1432                    @Override
1433                    public CmsADEConfigData getADEConfig() {
1434
1435                        return config;
1436                    }
1437
1438                    @Override
1439                    public CmsObject getCmsObject() {
1440
1441                        return cms;
1442                    }
1443
1444                    @Override
1445                    public CmsXmlContent getContent() {
1446
1447                        return originalCopy;
1448                    }
1449
1450                    @Override
1451                    public Locale getLocale() {
1452
1453                        return currentLocale;
1454                    }
1455
1456                    @Override
1457                    public String getParameter(String param) {
1458
1459                        throw new UnsupportedOperationException();
1460                    }
1461
1462                    public boolean isAborted() {
1463
1464                        return false;
1465                    }
1466
1467                    @Override
1468                    public void progress(String progressMessage) {
1469
1470                        throw new UnsupportedOperationException();
1471                    }
1472
1473                    @Override
1474                    public void setHtmlMessage(String message) {
1475
1476                        messageBuffer.delete(0, messageBuffer.length());
1477                        if (message != null) {
1478                            messageBuffer.append(message);
1479                        }
1480
1481                    }
1482
1483                    @Override
1484                    public void setNextLocale(Locale locale) {
1485
1486                        nextLocaleContainer[0] = locale;
1487
1488                    }
1489
1490                    @Override
1491                    public void setResult(CmsXmlContent result) {
1492
1493                        resultContainer[0] = result;
1494                    }
1495                });
1496            }
1497            CmsXmlContent resultContent = resultContainer[0];
1498            Locale nextLocale = nextLocaleContainer[0];
1499            getSessionCache().setCacheXmlContent(structureId, resultContent);
1500            CmsContentAugmentationDetails result = new CmsContentAugmentationDetails();
1501            result.setLocales(
1502                resultContent.getLocales().stream().map(locale -> locale.toString()).collect(Collectors.toList()));
1503            result.setNextLocale(nextLocale != null ? nextLocale.toString() : null);
1504            if (messageBuffer.length() > 0) {
1505                result.setHtmlMessage(messageBuffer.toString());
1506            }
1507            return result;
1508        } catch (Exception e) {
1509            error(e);
1510            return null;
1511        }
1512
1513    }
1514
1515    /**
1516     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#updateEntityHtml(org.opencms.acacia.shared.CmsEntity, java.lang.String, java.lang.String)
1517     */
1518    public CmsEntityHtml updateEntityHtml(CmsEntity entity, String contextUri, String htmlContextInfo)
1519    throws Exception {
1520
1521        CmsUUID structureId = CmsContentDefinition.entityIdToUuid(entity.getId());
1522        if (structureId != null) {
1523            CmsObject cms = getCmsObject();
1524            try {
1525                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1526                CmsFile file = cms.readFile(resource);
1527                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
1528                String entityId = entity.getId();
1529                Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
1530                if (content.hasLocale(contentLocale)) {
1531                    content.removeLocale(contentLocale);
1532                }
1533                content.addLocale(cms, contentLocale);
1534                addEntityAttributes(cms, content, "", entity, contentLocale);
1535                CmsValidationResult validationResult = validateContent(cms, structureId, content);
1536                String htmlContent = null;
1537                if (!validationResult.hasErrors()) {
1538                    file.setContents(content.marshal());
1539
1540                    JSONObject contextInfo = new JSONObject(htmlContextInfo);
1541                    String containerName = contextInfo.getString(CmsCntPageData.JSONKEY_NAME);
1542                    String containerType = contextInfo.getString(CmsCntPageData.JSONKEY_TYPE);
1543                    int containerWidth = contextInfo.getInt(CmsCntPageData.JSONKEY_WIDTH);
1544                    int maxElements = contextInfo.getInt(CmsCntPageData.JSONKEY_MAXELEMENTS);
1545                    boolean detailView = contextInfo.getBoolean(CmsCntPageData.JSONKEY_DETAILVIEW);
1546                    boolean isDetailViewContainer = contextInfo.getBoolean(
1547                        CmsCntPageData.JSONKEY_ISDETAILVIEWCONTAINER);
1548                    JSONObject presets = contextInfo.getJSONObject(CmsCntPageData.JSONKEY_PRESETS);
1549                    HashMap<String, String> presetsMap = new HashMap<String, String>();
1550                    for (String key : presets.keySet()) {
1551                        String val = presets.getString(key);
1552                        presetsMap.put(key, val);
1553                    }
1554                    CmsContainer container = new CmsContainer(
1555                        containerName,
1556                        containerType,
1557                        null,
1558                        containerWidth,
1559                        maxElements,
1560                        isDetailViewContainer,
1561                        detailView,
1562                        true,
1563                        Collections.<CmsContainerElement> emptyList(),
1564                        null,
1565                        null,
1566                        presetsMap);
1567                    CmsUUID detailContentId = null;
1568                    if (contextInfo.has(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID)) {
1569                        detailContentId = new CmsUUID(contextInfo.getString(CmsCntPageData.JSONKEY_DETAIL_ELEMENT_ID));
1570                    }
1571                    CmsElementUtil elementUtil = new CmsElementUtil(
1572                        cms,
1573                        contextUri,
1574                        detailContentId,
1575                        getThreadLocalRequest(),
1576                        getThreadLocalResponse(),
1577                        contentLocale);
1578                    htmlContent = elementUtil.getContentByContainer(
1579                        file,
1580                        contextInfo.getString(CmsCntPageData.JSONKEY_ELEMENT_ID),
1581                        container);
1582                }
1583                return new CmsEntityHtml(htmlContent, validationResult);
1584
1585            } catch (Exception e) {
1586                error(e);
1587            }
1588        }
1589        return null;
1590    }
1591
1592    /**
1593     * @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)
1594     */
1595    public CmsValidationResult validateEntities(
1596        CmsEntity lastEditedEntity,
1597        String clientId,
1598        List<String> deletedEntities,
1599        Collection<String> skipPaths,
1600        String lastEditedLocale)
1601    throws CmsRpcException {
1602
1603        CmsUUID structureId = null;
1604        if (lastEditedEntity != null) {
1605            structureId = CmsContentDefinition.entityIdToUuid(lastEditedEntity.getId());
1606        }
1607        if ((structureId == null) && !deletedEntities.isEmpty()) {
1608            structureId = CmsContentDefinition.entityIdToUuid(deletedEntities.get(0));
1609        }
1610        CmsADEConfigData configData = OpenCms.getADEManager().lookupConfiguration(
1611            getCmsObject(),
1612            getCmsObject().getRequestContext().getRootUri());
1613        if (structureId != null) {
1614            CmsObject cms = getCmsObject();
1615            CmsResource resource = null;
1616            try {
1617                resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1618                CmsFile file = cms.readFile(resource);
1619                CmsXmlContent content = getContentDocument(file, true);
1620                checkAutoCorrection(cms, content);
1621                if (lastEditedEntity != null) {
1622                    synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
1623                }
1624                for (String deleteId : deletedEntities) {
1625                    Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(deleteId));
1626                    if (content.hasLocale(contentLocale)) {
1627                        content.removeLocale(contentLocale);
1628                    }
1629                }
1630                return validateContent(cms, structureId, content);
1631
1632            } catch (Exception e) {
1633                if (resource != null) {
1634                    // TODO: What is this for?
1635                    getSessionCache().uncacheXmlContent(structureId);
1636                }
1637                error(e);
1638            }
1639        }
1640        return null;
1641    }
1642
1643    /**
1644     * @see org.opencms.acacia.shared.rpc.I_CmsContentService#validateEntity(org.opencms.acacia.shared.CmsEntity)
1645     */
1646    public CmsValidationResult validateEntity(CmsEntity changedEntity) throws CmsRpcException {
1647
1648        CmsUUID structureId = null;
1649        if (changedEntity == null) {
1650            return new CmsValidationResult(null, null);
1651        }
1652        structureId = CmsContentDefinition.entityIdToUuid(changedEntity.getId());
1653
1654        if (structureId != null) {
1655            CmsObject cms = getCmsObject();
1656            CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
1657                cms,
1658                cms.getRequestContext().getRootUri());
1659            Set<String> setFieldNames = Sets.newHashSet();
1660            try {
1661                CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION);
1662                CmsFile file = cms.readFile(resource);
1663                CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
1664                String entityId = changedEntity.getId();
1665                Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
1666                if (content.hasLocale(contentLocale)) {
1667                    content.removeLocale(contentLocale);
1668                }
1669                content.addLocale(cms, contentLocale);
1670                setFieldNames.addAll(addEntityAttributes(cms, content, "", changedEntity, contentLocale));
1671
1672                CmsValidationResult result = validateContent(cms, structureId, content, setFieldNames);
1673                CmsEntityAttribute clientIdAttr = changedEntity.getAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE);
1674                if (clientIdAttr != null) {
1675                    String clientId = clientIdAttr.getSimpleValue();
1676                    CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId);
1677                    I_CmsFormatterBean formatter = getFormatterForElement(config, containerElement);
1678                    if ((formatter != null)
1679                        && formatter.isAllowsSettingsInEditor()
1680                        && !formatter.getSettings(config).isEmpty()) {
1681                        Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings(
1682                            cms,
1683                            config,
1684                            formatter,
1685                            containerElement.getResource(),
1686                            contentLocale,
1687                            getRequest());
1688                        validateSettings(changedEntity, result, settingsConfig);
1689                    }
1690                }
1691                return result;
1692            } catch (Throwable e) {
1693                error(e);
1694            }
1695        }
1696        return new CmsValidationResult(null, null);
1697    }
1698
1699    /**
1700     * Decodes the newlink request parameter if possible.<p>
1701     *
1702     * @param newLink the parameter to decode
1703     *
1704     * @return the decoded value
1705     */
1706    protected String decodeNewLink(String newLink) {
1707
1708        String result = newLink;
1709        if (result == null) {
1710            return null;
1711        }
1712        try {
1713            result = CmsEncoder.decode(result);
1714            try {
1715                result = CmsEncoder.decode(result);
1716            } catch (Throwable e) {
1717                LOG.info(e.getLocalizedMessage(), e);
1718            }
1719        } catch (Throwable e) {
1720            LOG.info(e.getLocalizedMessage(), e);
1721        }
1722
1723        return result;
1724    }
1725
1726    /**
1727     * Returns the element name to the given element.<p>
1728     *
1729     * @param attributeName the attribute name
1730     *
1731     * @return the element name
1732     */
1733    protected String getElementName(String attributeName) {
1734
1735        if (attributeName.contains("/")) {
1736            return attributeName.substring(attributeName.lastIndexOf("/") + 1);
1737        }
1738        return attributeName;
1739    }
1740
1741    /**
1742     * Helper method to determine the encoding of the given file in the VFS,
1743     * which must be set using the "content-encoding" property.<p>
1744     *
1745     * @param cms the CmsObject
1746     * @param file the file which is to be checked
1747     * @return the encoding for the file
1748     */
1749    protected String getFileEncoding(CmsObject cms, CmsResource file) {
1750
1751        String result;
1752        try {
1753            result = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING, true).getValue(
1754                OpenCms.getSystemInfo().getDefaultEncoding());
1755        } catch (CmsException e) {
1756            result = OpenCms.getSystemInfo().getDefaultEncoding();
1757        }
1758        return CmsEncoder.lookupEncoding(result, OpenCms.getSystemInfo().getDefaultEncoding());
1759    }
1760
1761    /**
1762     * Parses the element into an entity.<p>
1763     *
1764     * @param content the entity content
1765     * @param element the current element
1766     * @param locale the content locale
1767     * @param entityId the entity id
1768     * @param parentPath the parent path
1769     * @param typeName the entity type name
1770     * @param visitor the content type visitor
1771     * @param includeInvisible include invisible attributes
1772     * @param editedLocalEntity the edited locale entity
1773     *
1774     * @return the entity
1775     */
1776    protected CmsEntity readEntity(
1777        CmsXmlContent content,
1778        Element element,
1779        Locale locale,
1780        String entityId,
1781        String parentPath,
1782        String typeName,
1783        CmsContentTypeVisitor visitor,
1784        boolean includeInvisible,
1785        CmsEntity editedLocalEntity) {
1786
1787        String newEntityId = entityId + (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath) ? "/" + parentPath : "");
1788        CmsEntity newEntity = new CmsEntity(newEntityId, typeName);
1789        CmsEntity result = newEntity;
1790
1791        List<Element> elements = element.elements();
1792        CmsType type = visitor.getTypes().get(typeName);
1793        boolean isChoice = type.isChoice();
1794        String choiceTypeName = null;
1795        // just needed for choice attributes
1796        Map<String, Integer> attributeCounter = null;
1797        if (isChoice) {
1798            choiceTypeName = type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME);
1799            type = visitor.getTypes().get(type.getAttributeTypeName(CmsType.CHOICE_ATTRIBUTE_NAME));
1800            attributeCounter = new HashMap<String, Integer>();
1801        }
1802        int counter = 0;
1803        CmsObject cms = getCmsObject();
1804        String previousName = null;
1805        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(parentPath)) {
1806            parentPath += "/";
1807        }
1808        for (Element child : elements) {
1809            String attributeName = getAttributeName(child.getName(), typeName);
1810            String subTypeName = type.getAttributeTypeName(attributeName);
1811            if (visitor.getTypes().get(subTypeName) == null) {
1812                // in case there is no type configured for this element, the schema may have changed, skip the element
1813                continue;
1814            }
1815            if (!includeInvisible && !visitor.getAttributeConfigurations().get(attributeName).isVisible()) {
1816                // skip attributes marked as invisible, there content should not be transfered to the client
1817                continue;
1818            }
1819            if (isChoice && (attributeCounter != null)) {
1820                if (!attributeName.equals(previousName)) {
1821                    if (attributeCounter.get(attributeName) != null) {
1822                        counter = attributeCounter.get(attributeName).intValue();
1823                    } else {
1824                        counter = 0;
1825                    }
1826                    previousName = attributeName;
1827                }
1828                attributeCounter.put(attributeName, Integer.valueOf(counter + 1));
1829            } else if (!attributeName.equals(previousName)) {
1830
1831                // reset the attribute counter for every attribute name
1832                counter = 0;
1833
1834                previousName = attributeName;
1835            }
1836            if (isChoice) {
1837                result = new CmsEntity(
1838                    newEntityId + "/" + CmsType.CHOICE_ATTRIBUTE_NAME + "_" + child.getName() + "[" + counter + "]",
1839                    choiceTypeName);
1840                newEntity.addAttributeValue(CmsType.CHOICE_ATTRIBUTE_NAME, result);
1841            }
1842            String path = parentPath + child.getName();
1843            if (visitor.isDynamicallyLoaded(attributeName)) {
1844                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
1845                String attributeValue = getDynamicAttributeValue(
1846                    content.getFile(),
1847                    value,
1848                    attributeName,
1849                    editedLocalEntity);
1850                result.addAttributeValue(attributeName, attributeValue);
1851            } else if (visitor.getTypes().get(subTypeName).isSimpleType()) {
1852                I_CmsXmlContentValue value = content.getValue(path, locale, counter);
1853                result.addAttributeValue(attributeName, value.getStringValue(cms));
1854            } else {
1855                CmsEntity editedSubEntity = null;
1856                if ((editedLocalEntity != null) && (editedLocalEntity.getAttribute(attributeName) != null)) {
1857                    editedSubEntity = editedLocalEntity.getAttribute(attributeName).getComplexValue();
1858                }
1859                CmsEntity subEntity = readEntity(
1860                    content,
1861                    child,
1862                    locale,
1863                    entityId,
1864                    path + "[" + (counter + 1) + "]",
1865                    subTypeName,
1866                    visitor,
1867                    includeInvisible,
1868                    editedSubEntity);
1869                result.addAttributeValue(attributeName, subEntity);
1870
1871            }
1872            counter++;
1873        }
1874        return newEntity;
1875    }
1876
1877    /**
1878     * Reads the types from the given content definition and adds the to the map of already registered
1879     * types if necessary.<p>
1880     *
1881     * @param xmlContentDefinition the XML content definition
1882     * @param locale the messages locale
1883     *
1884     * @return the types of the given content definition
1885     */
1886    protected Map<String, CmsType> readTypes(CmsXmlContentDefinition xmlContentDefinition, Locale locale) {
1887
1888        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), null, locale);
1889        visitor.visitTypes(xmlContentDefinition, locale);
1890        return visitor.getTypes();
1891    }
1892
1893    /**
1894     * Synchronizes the locale independent fields.<p>
1895     *
1896     * @param file the content file
1897     * @param content the XML content
1898     * @param skipPaths the paths to skip during locale synchronization
1899     * @param entities the edited entities
1900     * @param lastEdited the last edited locale
1901     *
1902     * @throws CmsXmlException if something goes wrong
1903     */
1904    protected void synchronizeLocaleIndependentFields(
1905        CmsFile file,
1906        CmsXmlContent content,
1907        Collection<String> skipPaths,
1908        Collection<CmsEntity> entities,
1909        Locale lastEdited)
1910    throws CmsXmlException {
1911
1912        CmsEntity lastEditedEntity = null;
1913        for (CmsEntity entity : entities) {
1914            if (lastEdited.equals(CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entity.getId())))) {
1915                lastEditedEntity = entity;
1916            } else {
1917                synchronizeLocaleIndependentForEntity(file, content, skipPaths, entity);
1918            }
1919        }
1920        if (lastEditedEntity != null) {
1921            // prepare the last edited last, to sync locale independent fields
1922            synchronizeLocaleIndependentForEntity(file, content, skipPaths, lastEditedEntity);
1923        }
1924    }
1925
1926    /**
1927     * Transfers values marked as invisible from the original entity to the target entity.<p>
1928     *
1929     * @param original the original entity
1930     * @param target the target entiy
1931     * @param visitor the type visitor holding the content type configuration
1932     */
1933    protected void transferInvisibleValues(CmsEntity original, CmsEntity target, CmsContentTypeVisitor visitor) {
1934
1935        List<String> invisibleAttributes = new ArrayList<String>();
1936        for (Entry<String, CmsAttributeConfiguration> configEntry : visitor.getAttributeConfigurations().entrySet()) {
1937            if (!configEntry.getValue().isVisible()) {
1938                invisibleAttributes.add(configEntry.getKey());
1939            }
1940        }
1941        CmsContentDefinition.transferValues(
1942            original,
1943            target,
1944            invisibleAttributes,
1945            visitor.getTypes(),
1946            visitor.getAttributeConfigurations(),
1947            true);
1948    }
1949
1950    /**
1951    * Adds the attribute values of the entity to the given XML content.<p>
1952    *
1953    * @param cms the current cms context
1954    * @param content the XML content
1955    * @param parentPath the parent path
1956    * @param entity the entity
1957    * @param contentLocale the content locale
1958    *
1959    * @return the set of xpaths of simple fields in the XML content which were set by this method
1960    */
1961    private Set<String> addEntityAttributes(
1962        CmsObject cms,
1963        CmsXmlContent content,
1964        String parentPath,
1965        CmsEntity entity,
1966        Locale contentLocale) {
1967
1968        Set<String> fieldsSet = Sets.newHashSet();
1969        addEntityAttributes(cms, content, parentPath, entity, contentLocale, fieldsSet);
1970        return fieldsSet;
1971    }
1972
1973    /**
1974     * Adds the attribute values of the entity to the given XML content.<p>
1975     *
1976     * @param cms the current cms context
1977     * @param content the XML content
1978     * @param parentPath the parent path
1979     * @param entity the entity
1980     * @param contentLocale the content locale
1981     * @param fieldsSet set to store which fields were set in the XML content
1982     */
1983    private void addEntityAttributes(
1984        CmsObject cms,
1985        CmsXmlContent content,
1986        String parentPath,
1987        CmsEntity entity,
1988        Locale contentLocale,
1989        Set<String> fieldsSet) {
1990
1991        for (CmsEntityAttribute attribute : entity.getAttributes()) {
1992            if (!isSettingsAttribute(attribute.getAttributeName())) {
1993                if (CmsType.CHOICE_ATTRIBUTE_NAME.equals(attribute.getAttributeName())) {
1994                    List<CmsEntity> choiceEntities = attribute.getComplexValues();
1995                    for (int i = 0; i < choiceEntities.size(); i++) {
1996                        List<CmsEntityAttribute> choiceAttributes = choiceEntities.get(i).getAttributes();
1997                        // each choice entity may only have a single attribute with a single value
1998                        assert (choiceAttributes.size() == 1)
1999                            && choiceAttributes.get(
2000                                0).isSingleValue() : "each choice entity may only have a single attribute with a single value";
2001                        CmsEntityAttribute choiceAttribute = choiceAttributes.get(0);
2002                        String elementPath = parentPath + getElementName(choiceAttribute.getAttributeName());
2003                        if (choiceAttribute.isSimpleValue()) {
2004                            String value = choiceAttribute.getSimpleValue();
2005                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
2006                            if (field == null) {
2007                                field = content.addValue(cms, elementPath, contentLocale, i);
2008                            }
2009                            field.setStringValue(cms, value);
2010                            fieldsSet.add(field.getPath());
2011                        } else {
2012                            CmsEntity child = choiceAttribute.getComplexValue();
2013                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
2014                            if (field == null) {
2015                                field = content.addValue(cms, elementPath, contentLocale, i);
2016                            }
2017                            addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
2018                        }
2019                    }
2020                } else {
2021                    String elementPath = parentPath + getElementName(attribute.getAttributeName());
2022                    if (attribute.isSimpleValue()) {
2023                        List<String> values = attribute.getSimpleValues();
2024                        for (int i = 0; i < values.size(); i++) {
2025                            String value = values.get(i);
2026                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
2027                            if (field == null) {
2028                                field = content.addValue(cms, elementPath, contentLocale, i);
2029                            }
2030                            field.setStringValue(cms, value);
2031                            fieldsSet.add(field.getPath());
2032                        }
2033                    } else {
2034                        List<CmsEntity> entities = attribute.getComplexValues();
2035                        for (int i = 0; i < entities.size(); i++) {
2036                            CmsEntity child = entities.get(i);
2037                            I_CmsXmlContentValue field = content.getValue(elementPath, contentLocale, i);
2038                            if (field == null) {
2039                                field = content.addValue(cms, elementPath, contentLocale, i);
2040                            }
2041                            addEntityAttributes(cms, content, field.getPath() + "/", child, contentLocale, fieldsSet);
2042                        }
2043                    }
2044                }
2045            }
2046        }
2047    }
2048
2049    /**
2050     * Adds the setting attributes according to the setting configuration.<p>
2051     *
2052     * @param attributeConfiguration the attribute configuration
2053     * @param settingsConfig the setting configuration
2054     * @param nestedFormatters the nested formatters
2055     * @param messages the messages
2056     * @param contentLocale the content locale
2057     * @param settingPresets the setting presets
2058     *
2059     * @return the list of names of added attributes
2060     */
2061    private List<String> addSettingsAttributes(
2062        Map<String, CmsAttributeConfiguration> attributeConfiguration,
2063        Map<String, CmsXmlContentProperty> settingsConfig,
2064        List<I_CmsFormatterBean> nestedFormatters,
2065        CmsMessages messages,
2066        Locale contentLocale,
2067        Map<String, String> settingPresets) {
2068
2069        String attrName;
2070        List<String> attributes = new ArrayList<String>();
2071        attributeConfiguration.put(
2072            SETTINGS_CLIENT_ID_ATTRIBUTE,
2073            new CmsAttributeConfiguration(
2074                "internal_client_id",
2075                "",
2076                null,
2077                null,
2078                null,
2079                null,
2080                DisplayType.none.name(),
2081                false,
2082                false,
2083                false));
2084        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
2085            CmsXmlContentProperty prop = entry.getValue();
2086            String niceName = prop.getNiceName();
2087            if (CmsStringUtil.isEmptyOrWhitespaceOnly(niceName)) {
2088                niceName = prop.getName();
2089            }
2090            attrName = getSettingsAttributeName(entry.getKey());
2091            boolean visible = !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(prop.getWidget())
2092                && !settingPresets.containsKey(prop.getName());
2093            if (visible) {
2094                attributes.add(attrName);
2095            }
2096
2097            attributeConfiguration.put(
2098                attrName,
2099                new CmsAttributeConfiguration(
2100                    niceName,
2101                    prop.getDescription(),
2102                    getWidgetName(prop.getWidget()),
2103                    getWidgetConfig(prop.getWidget(), prop.getWidgetConfiguration(), messages, contentLocale),
2104                    prop.getDefault(),
2105                    prop.getPlaceholder(),
2106                    DisplayType.singleline.name(),
2107                    visible,
2108                    false,
2109                    false));
2110        }
2111        if (nestedFormatters != null) {
2112            for (I_CmsFormatterBean formatter : nestedFormatters) {
2113                attrName = getSettingsAttributeName(formatter.getId());
2114                attributes.add(attrName);
2115                attributeConfiguration.put(
2116                    attrName,
2117                    new CmsAttributeConfiguration(
2118                        formatter.getNiceName(m_workplaceLocale),
2119                        "",
2120                        null,
2121                        null,
2122                        null,
2123                        null,
2124                        DisplayType.none.name(),
2125                        true,
2126                        false,
2127                        false));
2128
2129            }
2130        }
2131        return attributes;
2132    }
2133
2134    /**
2135     * Adds the entity types according to the setting configuration.<p>
2136     *
2137     * @param entityType the entity base type name
2138     * @param types the types
2139     * @param settingsConfig the setting configuration
2140     * @param nestedFormatters the nested formatters
2141     */
2142    private void addSettingsTypes(
2143        String entityType,
2144        Map<String, CmsType> types,
2145        Map<String, CmsXmlContentProperty> settingsConfig,
2146        List<I_CmsFormatterBean> nestedFormatters) {
2147
2148        CmsType baseType = types.get(entityType);
2149        CmsType settingType = new CmsType(SETTING_TYPE_NAME);
2150        types.put(settingType.getId(), settingType);
2151        baseType.addAttribute(SETTINGS_CLIENT_ID_ATTRIBUTE, settingType, 1, 1);
2152        // add main settings first
2153        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
2154            boolean nested = false;
2155            if (nestedFormatters != null) {
2156                for (I_CmsFormatterBean formatter : nestedFormatters) {
2157                    if (entry.getKey().startsWith(formatter.getId())) {
2158                        nested = true;
2159                        break;
2160                    }
2161                }
2162            }
2163            if (!nested) {
2164                baseType.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1);
2165            }
2166        }
2167        // add nested formatter settings after
2168        for (Entry<String, CmsXmlContentProperty> entry : settingsConfig.entrySet()) {
2169            if (nestedFormatters != null) {
2170                for (I_CmsFormatterBean formatter : nestedFormatters) {
2171                    if (entry.getKey().startsWith(formatter.getId())
2172                        && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(entry.getValue().getWidget())) {
2173                        CmsType parent = types.get(formatter.getId());
2174                        if (parent == null) {
2175                            parent = new CmsType(formatter.getId());
2176                            types.put(parent.getId(), parent);
2177                            baseType.addAttribute(getSettingsAttributeName(formatter.getId()), parent, 1, 1);
2178                        }
2179                        parent.addAttribute(getSettingsAttributeName(entry.getKey()), settingType, 0, 1);
2180                        break;
2181                    }
2182                }
2183            }
2184        }
2185    }
2186
2187    /**
2188     * Adds the settings values to the given entity.<p>
2189     *
2190     * @param entity the entity
2191     * @param containerElement the container element bean providing the values
2192     * @param nestedFormatters the nested formatters
2193     */
2194    private void addSettingsValues(
2195        CmsEntity entity,
2196        CmsContainerElementBean containerElement,
2197        List<I_CmsFormatterBean> nestedFormatters) {
2198
2199        entity.addAttributeValue(SETTINGS_CLIENT_ID_ATTRIBUTE, containerElement.editorHash());
2200        for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) {
2201            boolean nested = false;
2202            if (nestedFormatters != null) {
2203                for (I_CmsFormatterBean formatter : nestedFormatters) {
2204                    if (settingEntry.getKey().startsWith(formatter.getId())) {
2205                        String nestedSettingAttributeName = getSettingsAttributeName(formatter.getId());
2206                        CmsEntity nestedEntity = null;
2207                        CmsEntityAttribute attribute = entity.getAttribute(nestedSettingAttributeName);
2208                        if (attribute != null) {
2209                            nestedEntity = attribute.getComplexValue();
2210                        } else {
2211                            nestedEntity = new CmsEntity(nestedSettingAttributeName + "[1]", formatter.getId());
2212                            entity.addAttributeValue(nestedSettingAttributeName, nestedEntity);
2213                        }
2214                        nestedEntity.addAttributeValue(
2215                            getSettingsAttributeName(settingEntry.getKey()),
2216                            settingEntry.getValue());
2217                        nested = true;
2218                        break;
2219                    }
2220                }
2221            }
2222            if (!nested) {
2223                entity.addAttributeValue(getSettingsAttributeName(settingEntry.getKey()), settingEntry.getValue());
2224            }
2225
2226        }
2227    }
2228
2229    /**
2230     * Check if automatic content correction is required. Returns <code>true</code> if the content was changed.<p>
2231     *
2232     * @param cms the cms context
2233     * @param content the content to check
2234     *
2235     * @return <code>true</code> if the content was changed
2236     * @throws CmsXmlException if the automatic content correction failed
2237     */
2238    private boolean checkAutoCorrection(CmsObject cms, CmsXmlContent content) throws CmsXmlException {
2239
2240        boolean performedAutoCorrection = content.isTransformedVersion();
2241        try {
2242            content.validateXmlStructure(new CmsXmlEntityResolver(cms));
2243        } catch (CmsXmlException eXml) {
2244            // validation failed
2245            content.setAutoCorrectionEnabled(true);
2246            content.correctXmlStructure(cms);
2247            performedAutoCorrection = true;
2248        }
2249        return performedAutoCorrection;
2250    }
2251
2252    /**
2253     * Creates a new resource to edit, delegating to an edit handler if edit handler data is passed in.<p>
2254     *
2255     * @param newLink A string, specifying where which new content should be created.
2256     * @param locale The locale for which the
2257     * @param referenceSitePath site path of the currently edited content.
2258     * @param modelFileSitePath site path of the model file
2259     * @param mode optional creation mode
2260     * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created.
2261     * @param editHandlerData edit handler data (will be null if no edit handler is configured or the edit handler does not handle 'new')
2262     *
2263     * @return The site-path of the newly created resource.
2264     * @throws CmsException if something goes wrong
2265     */
2266    private String createResourceToEdit(
2267        String newLink,
2268        Locale locale,
2269        String referenceSitePath,
2270        String modelFileSitePath,
2271        String mode,
2272        String postCreateHandler,
2273        CmsEditHandlerData editHandlerData)
2274    throws CmsException {
2275
2276        CmsObject cms = getCmsObject();
2277        HttpServletRequest request = getRequest();
2278        if (editHandlerData != null) {
2279            CmsContainerpageService containerpageService = new CmsContainerpageService();
2280            containerpageService.setCms(cms);
2281            containerpageService.setRequest(request);
2282            CmsResource page = cms.readResource(editHandlerData.getPageContextId(), CmsResourceFilter.ALL);
2283            CmsContainerElementBean elementBean = containerpageService.getCachedElement(
2284                editHandlerData.getClientId(),
2285                page.getRootPath());
2286            Map<String, String[]> params = CmsRequestUtil.createParameterMap(
2287                CmsEncoder.decode(editHandlerData.getRequestParams()),
2288                true,
2289                CmsEncoder.ENCODING_UTF_8);
2290            elementBean.initResource(cms);
2291            CmsResource elementResource = elementBean.getResource();
2292            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(elementResource);
2293            if (type instanceof CmsResourceTypeXmlContent) {
2294                CmsResourceTypeXmlContent xmlType = (CmsResourceTypeXmlContent)type;
2295                I_CmsEditHandler handler = xmlType.getEditHandler(cms);
2296                if (handler != null) {
2297                    return handler.handleNew(
2298                        cms,
2299                        newLink,
2300                        locale,
2301                        referenceSitePath,
2302                        modelFileSitePath,
2303                        postCreateHandler,
2304                        elementBean,
2305                        editHandlerData.getPageContextId(),
2306                        params,
2307                        editHandlerData.getOption());
2308
2309                } else {
2310                    LOG.warn(
2311                        "Invalid state: edit handler data passed in, but edit handler is undefined. type = "
2312                            + type.getTypeName());
2313                }
2314            } else {
2315                LOG.warn("Invalid state: edit handler data passed in for non-XML content type.");
2316            }
2317        }
2318        return defaultCreateResourceToEdit(
2319            cms,
2320            newLink,
2321            locale,
2322            referenceSitePath,
2323            modelFileSitePath,
2324            mode,
2325            postCreateHandler);
2326    }
2327
2328    /**
2329     * Evaluates any wildcards in the given scope and returns all allowed permutations of it.<p>
2330     *
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     * @param scope the scope
2335     * @param definition the content definition
2336     *
2337     * @return the evaluate scope permutations
2338     */
2339    private Set<String> evaluateScope(String scope, CmsXmlContentDefinition definition) {
2340
2341        Set<String> evaluatedScopes = new HashSet<String>();
2342        if (scope.contains("*")) {
2343            // evaluate wildcards to get all allowed permutations of the scope
2344            // a path like Paragraph*/Image should result in Paragraph[0]/Image, Paragraph[1]/Image and Paragraph[2]/Image
2345            // in case max occurrence for Paragraph is 3
2346
2347            String[] pathElements = scope.split("/");
2348            String parentPath = "";
2349
2350            for (int i = 0; i < pathElements.length; i++) {
2351                String elementName = pathElements[i];
2352                boolean hasWildCard = elementName.endsWith("*");
2353
2354                if (hasWildCard) {
2355                    elementName = elementName.substring(0, elementName.length() - 1);
2356                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
2357                    I_CmsXmlSchemaType type = definition.getSchemaType(parentPath);
2358                    Set<String> tempScopes = new HashSet<String>();
2359                    if (type.getMaxOccurs() == Integer.MAX_VALUE) {
2360                        throw new IllegalStateException(
2361                            "Can not use fields with unbounded maxOccurs in scopes for editor change handler.");
2362                    }
2363                    for (int j = 0; j < type.getMaxOccurs(); j++) {
2364                        if (evaluatedScopes.isEmpty()) {
2365                            tempScopes.add(elementName + "[" + (j + 1) + "]");
2366                        } else {
2367
2368                            for (String evScope : evaluatedScopes) {
2369                                tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName + "[" + (j + 1) + "]"));
2370                            }
2371                        }
2372                    }
2373                    evaluatedScopes = tempScopes;
2374                } else {
2375                    parentPath = CmsStringUtil.joinPaths(parentPath, elementName);
2376                    Set<String> tempScopes = new HashSet<String>();
2377                    if (evaluatedScopes.isEmpty()) {
2378                        tempScopes.add(elementName);
2379                    } else {
2380                        for (String evScope : evaluatedScopes) {
2381                            tempScopes.add(CmsStringUtil.joinPaths(evScope, elementName));
2382                        }
2383                    }
2384                    evaluatedScopes = tempScopes;
2385                }
2386            }
2387        } else {
2388            evaluatedScopes.add(scope);
2389        }
2390        return evaluatedScopes;
2391    }
2392
2393    /**
2394     * Evaluates the values of the locale independent fields and the paths to skip during locale synchronization.<p>
2395     *
2396     * @param content the XML content
2397     * @param syncValues the map of synchronization values
2398     * @param skipPaths the list o paths to skip
2399     */
2400    private void evaluateSyncLocaleValues(
2401        CmsXmlContent content,
2402        Map<String, String> syncValues,
2403        Collection<String> skipPaths) {
2404
2405        CmsObject cms = getCmsObject();
2406        for (Locale locale : content.getLocales()) {
2407            for (String elementPath : content.getContentDefinition().getContentHandler().getSynchronizations(
2408                true).getSynchronizationPaths()) {
2409                for (I_CmsXmlContentValue contentValue : content.getSimpleValuesBelowPath(elementPath, locale)) {
2410                    String valuePath = contentValue.getPath();
2411                    boolean skip = false;
2412                    for (String skipPath : skipPaths) {
2413                        if (valuePath.startsWith(skipPath)) {
2414                            skip = true;
2415                            break;
2416                        }
2417                    }
2418                    if (!skip) {
2419                        String value = contentValue.getStringValue(cms);
2420                        if (syncValues.containsKey(valuePath)) {
2421                            if (!syncValues.get(valuePath).equals(value)) {
2422                                // in case the current value does not match the previously stored value,
2423                                // remove it and add the parent path to the skipPaths list
2424                                syncValues.remove(valuePath);
2425                                int pathLevelDiff = (CmsResource.getPathLevel(valuePath)
2426                                    - CmsResource.getPathLevel(elementPath)) + 1;
2427                                for (int i = 0; i < pathLevelDiff; i++) {
2428                                    valuePath = CmsXmlUtils.removeLastXpathElement(valuePath);
2429                                }
2430                                skipPaths.add(valuePath);
2431                            }
2432                        } else {
2433                            syncValues.put(valuePath, value);
2434                        }
2435                    }
2436                }
2437            }
2438        }
2439    }
2440
2441    /**
2442     * Returns the change handler scopes.<p>
2443     *
2444     * @param definition the content definition
2445     *
2446     * @return the scopes
2447     */
2448    private Set<String> getChangeHandlerScopes(CmsXmlContentDefinition definition) {
2449
2450        List<I_CmsXmlContentEditorChangeHandler> changeHandlers = definition.getContentHandler().getEditorChangeHandlers(
2451            false);
2452        Set<String> scopes = new HashSet<String>();
2453        for (I_CmsXmlContentEditorChangeHandler handler : changeHandlers) {
2454            String scope = handler.getScope();
2455            scopes.addAll(evaluateScope(scope, definition));
2456        }
2457        return scopes;
2458    }
2459
2460    /**
2461     * Returns the active content augmentation, if any - otherwise returns null.
2462     *
2463     * @param cms the CMS context
2464     * @param resource the edited resource
2465     * @param config the active sitemap config
2466     *
2467     * @return the content augmentation, or null if none is active
2468     */
2469    private I_CmsXmlContentAugmentation getContentAugmentation(
2470        CmsObject cms,
2471        CmsResource resource,
2472        CmsADEConfigData config) {
2473
2474        String jsp = config.getAttribute("xml.content.augmentation.jsp", null);
2475
2476        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(jsp)) {
2477            jsp = jsp.trim();
2478            return new CmsJspContentAugmentation(jsp);
2479        } else {
2480            String className = config.getAttribute("xml.content.augmentation.class", null);
2481            if (className != null) {
2482                className = className.trim();
2483                try {
2484                    return (I_CmsXmlContentAugmentation)(Class.forName(className).getConstructor().newInstance());
2485                } catch (Exception e) {
2486                    LOG.error(e.getLocalizedMessage(), e);
2487                }
2488            }
2489            return null;
2490        }
2491    }
2492
2493    /**
2494     * Returns the XML content document.<p>
2495     *
2496     * @param file the resource file
2497     * @param fromCache <code>true</code> to use the cached document
2498     *
2499     * @return the content document
2500     *
2501     * @throws CmsXmlException if reading the XML fails
2502     */
2503    private CmsXmlContent getContentDocument(CmsFile file, boolean fromCache) throws CmsXmlException {
2504
2505        CmsXmlContent content = null;
2506        if (fromCache) {
2507            content = getSessionCache().getCacheXmlContent(file.getStructureId());
2508        }
2509        if (content == null) {
2510            content = CmsXmlContentFactory.unmarshal(getCmsObject(), file);
2511            getSessionCache().setCacheXmlContent(file.getStructureId(), content);
2512        }
2513        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(getCmsObject(), file, Locale.ENGLISH);
2514        visitor.visitTypes(content.getContentDefinition(), Locale.ENGLISH);
2515        CmsDynamicCategoryFieldList dynCatFields = visitor.getOptionalDynamicCategoryFields();
2516        dynCatFields.ensureFields(getCmsObject(), content);
2517        return content;
2518    }
2519
2520    /**
2521     * Returns the value that has to be set for the dynamic attribute.
2522     *
2523     * @param file the file where the current content is stored
2524     * @param value the content value that is represented by the attribute
2525     * @param attributeName the attribute's name
2526     * @param editedLocalEntity the entities that where edited last
2527     * @return the value that has to be set for the dynamic attribute.
2528     */
2529    private String getDynamicAttributeValue(
2530        CmsFile file,
2531        I_CmsXmlContentValue value,
2532        String attributeName,
2533        CmsEntity editedLocalEntity) {
2534
2535        if ((null != editedLocalEntity) && (editedLocalEntity.getAttribute(attributeName) != null)) {
2536            getSessionCache().setDynamicValue(
2537                attributeName,
2538                editedLocalEntity.getAttribute(attributeName).getSimpleValue());
2539        }
2540        String currentValue = getSessionCache().getDynamicValue(attributeName);
2541        if (null != currentValue) {
2542            return currentValue;
2543        }
2544        if (null != file) {
2545            if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
2546                List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
2547                try {
2548                    categories = CmsCategoryService.getInstance().readResourceCategories(getCmsObject(), file);
2549                } catch (CmsException e) {
2550                    LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
2551                }
2552                I_CmsWidget widget = null;
2553                widget = CmsWidgetUtil.collectWidgetInfo(getCmsObject(), value).getWidget();
2554                if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
2555                    String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory(
2556                        getCmsObject(),
2557                        getCmsObject().getSitePath(file));
2558                    StringBuffer pathes = new StringBuffer();
2559                    for (CmsCategory category : categories) {
2560                        if (category.getPath().startsWith(mainCategoryPath)) {
2561                            String path = category.getBasePath() + category.getPath();
2562                            path = getCmsObject().getRequestContext().removeSiteRoot(path);
2563                            pathes.append(path).append(',');
2564                        }
2565                    }
2566                    String dynamicConfigString = pathes.length() > 0 ? pathes.substring(0, pathes.length() - 1) : "";
2567                    getSessionCache().setDynamicValue(attributeName, dynamicConfigString);
2568                    return dynamicConfigString;
2569                }
2570            } else if (value.getTypeName().equals(CmsXmlAccessRestrictionValue.TYPE_NAME)) {
2571                CmsAccessRestrictionInfo restrictionInfo = CmsAccessRestrictionInfo.getRestrictionInfo(
2572                    getCmsObject(),
2573                    value.getDocument().getContentDefinition());
2574                if (restrictionInfo != null) {
2575                    try {
2576                        List<CmsAccessControlEntry> aces = getCmsObject().getAccessControlEntries(
2577                            getCmsObject().getSitePath(value.getDocument().getFile()));
2578                        boolean hasEntry = aces.stream().anyMatch(
2579                            ace -> ace.getPrincipal().equals(restrictionInfo.getGroup().getId())
2580                                && ace.isResponsible());
2581                        return "" + hasEntry;
2582                    } catch (CmsException e) {
2583                        LOG.error(e.getLocalizedMessage(), e);
2584                    }
2585                    return "false";
2586                }
2587            }
2588        }
2589        return "";
2590
2591    }
2592
2593    /**
2594     * Returns the formatter configuration for the given container element bean.<p>
2595     *
2596     * @param containerElement the container element
2597     *
2598     * @return the formatter configuration
2599     */
2600    private I_CmsFormatterBean getFormatterForElement(
2601        CmsADEConfigData config,
2602        CmsContainerElementBean containerElement) {
2603
2604        if ((containerElement != null)
2605            && (containerElement.getFormatterId() != null)
2606            && !containerElement.getFormatterId().isNullUUID()) {
2607            CmsUUID formatterId = containerElement.getFormatterId();
2608
2609            // find formatter config setting
2610            for (Entry<String, String> settingEntry : containerElement.getIndividualSettings().entrySet()) {
2611                if (settingEntry.getKey().startsWith(CmsFormatterConfig.FORMATTER_SETTINGS_KEY)) {
2612                    String formatterConfigId = settingEntry.getValue();
2613                    I_CmsFormatterBean dynamicFmt = config.findFormatter(formatterConfigId);
2614                    if ((dynamicFmt != null) && dynamicFmt.getJspStructureId().equals(formatterId)) {
2615                        return dynamicFmt;
2616                    }
2617                }
2618            }
2619        }
2620        return null;
2621    }
2622
2623    /**
2624     * Returns the path elements for the given content value.<p>
2625     *
2626     * @param content the XML content
2627     * @param value the content value
2628     *
2629     * @return the path elements
2630     */
2631    private String[] getPathElements(CmsXmlContent content, I_CmsXmlContentValue value) {
2632
2633        List<String> pathElements = new ArrayList<String>();
2634        String[] paths = value.getPath().split("/");
2635        String path = "";
2636        for (int i = 0; i < paths.length; i++) {
2637            path += paths[i];
2638            I_CmsXmlContentValue ancestor = content.getValue(path, value.getLocale());
2639            int valueIndex = ancestor.getXmlIndex();
2640            if (ancestor.isChoiceOption()) {
2641                Element parent = ancestor.getElement().getParent();
2642                valueIndex = parent.indexOf(ancestor.getElement());
2643            }
2644            String pathElement = getAttributeName(ancestor);
2645            pathElements.add(pathElement + "[" + valueIndex + "]");
2646            if (ancestor.isChoiceType()) {
2647                pathElements.add("ATTRIBUTE_CHOICE");
2648            }
2649            path += "/";
2650        }
2651        return pathElements.toArray(new String[pathElements.size()]);
2652    }
2653
2654    /**
2655     * Returns the session cache.<p>
2656     *
2657     * @return the session cache
2658     */
2659    private CmsADESessionCache getSessionCache() {
2660
2661        if (m_sessionCache == null) {
2662            m_sessionCache = CmsADESessionCache.getCache(getRequest(), getCmsObject());
2663        }
2664        return m_sessionCache;
2665    }
2666
2667    /**
2668     * Returns the attribute name to use for the given setting name.<p>
2669     *
2670     * @param settingName the setting name
2671     *
2672     * @return the attribute name
2673     */
2674    private String getSettingsAttributeName(String settingName) {
2675
2676        return "/" + SETTINGS_ATTRIBUTE_NAME_PREFIX + settingName;
2677    }
2678
2679    /**
2680     * Returns information on validation issues, sorted by locale and in the order the issues appear in the editor.
2681     * @param cms the current context
2682     * @param content the content with the issues
2683     * @param validationResult the validation result
2684     * @return information on validation issues, sorted by locale and in the order the issues appear in the editor.
2685     */
2686    private Map<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> getValidationIssues(
2687        CmsObject cms,
2688        CmsXmlContent content,
2689        CmsValidationResult validationResult) {
2690
2691        // only if we have warnings or errors, we can display them.
2692        if (validationResult.hasErrors() || validationResult.hasWarnings()) {
2693            // if we have errors, we display errors and warnings mixed.
2694            Map<String, Map<String[], CmsPair<String, String>>> issues = new HashMap<>();
2695            if (validationResult.hasErrors()) {
2696                validationResult.getErrors().entrySet().forEach(
2697                    e -> issues.put(CmsContentDefinition.getLocaleFromId(e.getKey()), e.getValue()));
2698            }
2699            if (validationResult.hasWarnings()) {
2700                validationResult.getWarnings().entrySet().forEach(e -> {
2701                    String locale = CmsContentDefinition.getLocaleFromId(e.getKey());
2702                    if (issues.containsKey(locale)) {
2703                        // We assume we cannot have a warning and error at the same time, so we can override here.
2704                        issues.get(locale).putAll(e.getValue());
2705                    } else {
2706                        issues.put(locale, e.getValue());
2707                    }
2708                });
2709            }
2710            // we use a tree map to sort the locales alphabetically
2711            TreeMap<String, List<CmsPair<List<CmsPair<String, Integer>>, String>>> sortedInfoPerLocale = new TreeMap<>();
2712            CmsXmlContentDefinition definition = content.getContentDefinition();
2713            for (Entry<String, Map<String[], CmsPair<String, String>>> e : issues.entrySet()) {
2714                Locale l = CmsLocaleManager.getLocale(e.getKey());
2715                // map from XML path to attributes as provided by the validation result
2716                Map<String, CmsPair<String[], String>> errorsByPath = new HashMap<>(e.getValue().size());
2717                // fill the map
2718                e.getValue().entrySet().stream().forEach(
2719                    v -> errorsByPath.put(
2720                        v.getValue().getSecond(),
2721                        new CmsPair<>(v.getKey(), v.getValue().getFirst())));
2722                // get the paths and sort them
2723                List<String> sortedPaths = new ArrayList<>(errorsByPath.keySet());
2724                sortedPaths.sort(new CmsXmlDisplayOrderPathComparator(definition));
2725                // the infos for the locale, a list of issue information, where each issue information
2726                // is a list of the path part attributes combined with the index of the path part.
2727                List<CmsPair<List<CmsPair<String, Integer>>, String>> sortedInfos = new ArrayList<>(
2728                    errorsByPath.size());
2729                // fill the info list in the correct sort order
2730                for (String p : sortedPaths) {
2731                    CmsPair<String[], String> v = errorsByPath.get(p);
2732                    String[] entities = v.getFirst();
2733                    List<CmsPair<String, Integer>> singleInfo = new ArrayList<>(entities.length);
2734                    String attributePrefix = "";
2735                    for (int i = 0; i < entities.length; i++) {
2736                        String w = entities[i];
2737                        if (!"ATTRIBUTE_CHOICE".equals(w)) {
2738                            String attr = org.opencms.acacia.shared.CmsContentDefinition.removeIndex(w);
2739                            int idx = org.opencms.acacia.shared.CmsContentDefinition.extractIndex(w);
2740                            if (!attributePrefix.isEmpty()) {
2741                                attr = attributePrefix + ":" + attr;
2742                            }
2743                            singleInfo.add(new CmsPair<>(attr, Integer.valueOf(idx + 1)));
2744                            attributePrefix += attr.substring(attr.lastIndexOf('/'));
2745                        }
2746                    }
2747                    sortedInfos.add(new CmsPair<>(singleInfo, v.getSecond()));
2748                }
2749
2750                sortedInfoPerLocale.put(l.getDisplayName(getWorkplaceLocale(cms)), sortedInfos);
2751            }
2752            return sortedInfoPerLocale;
2753        }
2754        return null;
2755    }
2756
2757    /**
2758     * Transforms the widget configuration.<p>
2759     * @param settingsWidget the settings widget name
2760     * @param settingsConfig the setting widget configuration
2761     * @param messages the workplace messages
2762     * @param contentLocale the content locale
2763     *
2764     * @return the client widget configuration string
2765     */
2766    private String getWidgetConfig(
2767        String settingsWidget,
2768        String settingsConfig,
2769        CmsMessages messages,
2770        Locale contentLocale) {
2771
2772        Class<? extends I_CmsADEWidget> widgetClass = WIDGET_MAPPINGS.get(settingsWidget);
2773        String config = "";
2774        if (widgetClass == null) {
2775            widgetClass = CmsInputWidget.class;
2776        }
2777        try {
2778            I_CmsADEWidget widget = widgetClass.newInstance();
2779            widget.setConfiguration(settingsConfig);
2780            config = widget.getConfiguration(getCmsObject(), null, messages, null, contentLocale);
2781        } catch (Exception e) {
2782            LOG.error(e.getLocalizedMessage(), e);
2783        }
2784        return config;
2785    }
2786
2787    /**
2788     * Returns the widget class name.<p>
2789     *
2790     * @param settingsWidget the settings widget name
2791     *
2792     * @return the widget class name
2793     */
2794    private String getWidgetName(String settingsWidget) {
2795
2796        if (WIDGET_MAPPINGS.containsKey(settingsWidget)) {
2797            return WIDGET_MAPPINGS.get(settingsWidget).getName();
2798        } else {
2799            return CmsInputWidget.class.getName();
2800        }
2801    }
2802
2803    /**
2804     * Returns the workplace locale.<p>
2805     *
2806     * @param cms the current OpenCms context
2807     *
2808     * @return the current users workplace locale
2809     */
2810    private Locale getWorkplaceLocale(CmsObject cms) {
2811
2812        if (m_workplaceLocale == null) {
2813            m_workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
2814        }
2815        return m_workplaceLocale;
2816    }
2817
2818    /**
2819     * Checks whether the given attribute name indicates it is a settings attribute.<p>
2820     *
2821     * @param attributeName the attribute name
2822     *
2823     * @return <code>true</code> in case of settings attributes
2824     */
2825    private boolean isSettingsAttribute(String attributeName) {
2826
2827        return attributeName.startsWith("/" + SETTINGS_ATTRIBUTE_NAME_PREFIX);
2828    }
2829
2830    private CmsADEConfigData readConfig(CmsUUID pageId) {
2831
2832        if (pageId == null) {
2833            return null;
2834        }
2835        try {
2836            CmsResource resource = getCmsObject().readResource(pageId, CmsResourceFilter.IGNORE_EXPIRATION);
2837            return OpenCms.getADEManager().lookupConfiguration(getCmsObject(), resource.getRootPath());
2838        } catch (CmsException e) {
2839            LOG.warn(e.getLocalizedMessage(), e);
2840            return null;
2841        }
2842
2843    }
2844
2845    /**
2846     * Reads the content definition for the given resource and locale.<p>
2847     *
2848     * @param file the resource file
2849     * @param content the XML content
2850     * @param entityId the entity id
2851     * @param clientId the container element client id if available
2852     * @param locale the content locale
2853     * @param newLocale if the locale content should be created as new
2854     * @param mainLocale the main language to copy in case the element language node does not exist yet
2855     * @param editedLocaleEntity the edited locale entity
2856     * @param settingPresets the presets for settings
2857     * @param configData the sitemap configuration to use
2858     *
2859     * @return the content definition
2860     *
2861     * @throws CmsException if something goes wrong
2862     */
2863    private CmsContentDefinition readContentDefinition(
2864        CmsFile file,
2865        CmsXmlContent content,
2866        String entityId,
2867        String clientId,
2868        Locale locale,
2869        boolean newLocale,
2870        Locale mainLocale,
2871        CmsEntity editedLocaleEntity,
2872        Map<String, String> settingPresets,
2873        CmsADEConfigData configData)
2874    throws CmsException {
2875
2876        long timer = 0;
2877        if (LOG.isDebugEnabled()) {
2878            timer = System.currentTimeMillis();
2879        }
2880        CmsObject cms = getCmsObject();
2881        String lookupPath = cms.getRequestContext().getRootUri();
2882        if (lookupPath.startsWith("/system/workplace/") && (file != null)) {
2883            lookupPath = file.getRootPath();
2884        }
2885        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(cms, lookupPath);
2886        List<Locale> availableLocalesList = OpenCms.getLocaleManager().getAvailableLocales(cms, file);
2887        if (!availableLocalesList.contains(locale)) {
2888            availableLocalesList.retainAll(content.getLocales());
2889            List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(cms, file);
2890            Locale replacementLocale = OpenCms.getLocaleManager().getBestMatchingLocale(
2891                locale,
2892                defaultLocales,
2893                availableLocalesList);
2894            LOG.info(
2895                "Can't edit locale "
2896                    + locale
2897                    + " of file "
2898                    + file.getRootPath()
2899                    + " because it is not configured as available locale. Using locale "
2900                    + replacementLocale
2901                    + " instead.");
2902            locale = replacementLocale;
2903            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
2904        }
2905
2906        if (CmsStringUtil.isEmptyOrWhitespaceOnly(entityId)) {
2907            entityId = CmsContentDefinition.uuidToEntityId(file.getStructureId(), locale.toString());
2908        }
2909        boolean performedAutoCorrection = checkAutoCorrection(cms, content);
2910        if (performedAutoCorrection) {
2911            content.initDocument();
2912        }
2913        if (LOG.isDebugEnabled()) {
2914            LOG.debug(
2915                Messages.get().getBundle().key(
2916                    Messages.LOG_TAKE_UNMARSHALING_TIME_1,
2917                    "" + (System.currentTimeMillis() - timer)));
2918        }
2919        CmsContentTypeVisitor visitor = new CmsContentTypeVisitor(cms, file, locale);
2920        if (LOG.isDebugEnabled()) {
2921            timer = System.currentTimeMillis();
2922        }
2923        visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
2924        if (LOG.isDebugEnabled()) {
2925            LOG.debug(
2926                Messages.get().getBundle().key(
2927                    Messages.LOG_TAKE_VISITING_TYPES_TIME_1,
2928                    "" + (System.currentTimeMillis() - timer)));
2929        }
2930        CmsEntity entity = null;
2931        Map<String, String> syncValues = new HashMap<String, String>();
2932        Collection<String> skipPaths = new HashSet<String>();
2933        evaluateSyncLocaleValues(content, syncValues, skipPaths);
2934        if (content.hasLocale(locale) && newLocale) {
2935            // a new locale is requested, so remove the present one
2936            content.removeLocale(locale);
2937        }
2938        if (!content.hasLocale(locale)) {
2939            if ((mainLocale != null) && content.hasLocale(mainLocale)) {
2940                content.copyLocale(mainLocale, locale);
2941            } else {
2942                content.addLocale(cms, locale);
2943            }
2944            // sync the locale values
2945            if (!visitor.getLocaleSynchronizations().isEmpty() && (content.getLocales().size() > 1)) {
2946                for (Locale contentLocale : content.getLocales()) {
2947                    if (!contentLocale.equals(locale)) {
2948                        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
2949                    }
2950                }
2951            }
2952        }
2953        visitor.getOptionalDynamicCategoryFields().ensureFields(cms, content, locale);
2954        Element element = content.getLocaleNode(locale);
2955        if (LOG.isDebugEnabled()) {
2956            timer = System.currentTimeMillis();
2957        }
2958        entity = readEntity(
2959            content,
2960            element,
2961            locale,
2962            entityId,
2963            "",
2964            getTypeUri(content.getContentDefinition()),
2965            visitor,
2966            false,
2967            editedLocaleEntity);
2968
2969        if (LOG.isDebugEnabled()) {
2970            LOG.debug(
2971                Messages.get().getBundle().key(
2972                    Messages.LOG_TAKE_READING_ENTITY_TIME_1,
2973                    "" + (System.currentTimeMillis() - timer)));
2974        }
2975        List<String> contentLocales = new ArrayList<String>();
2976        for (Locale contentLocale : content.getLocales()) {
2977            contentLocales.add(contentLocale.toString());
2978        }
2979        Locale workplaceLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
2980        LinkedHashMap<String, String> availableLocales = new LinkedHashMap<String, String>();
2981        for (Locale availableLocale : OpenCms.getLocaleManager().getAvailableLocales(cms, file)) {
2982            availableLocales.put(availableLocale.toString(), availableLocale.getDisplayName(workplaceLocale));
2983        }
2984        String title = cms.readPropertyObject(file, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue();
2985        try {
2986            CmsGallerySearchResult searchResult = CmsGallerySearch.searchById(cms, file.getStructureId(), locale);
2987            title = searchResult.getTitle();
2988        } catch (CmsException e) {
2989            LOG.warn(e.getLocalizedMessage(), e);
2990        }
2991        String typeName = OpenCms.getResourceManager().getResourceType(file.getTypeId()).getTypeName();
2992        boolean autoUnlock = OpenCms.getWorkplaceManager().shouldAcaciaUnlock();
2993        Map<String, CmsEntity> entities = new HashMap<String, CmsEntity>();
2994        entities.put(entityId, entity);
2995
2996        Map<String, CmsAttributeConfiguration> attrConfig = visitor.getAttributeConfigurations();
2997        Map<String, CmsType> types = visitor.getTypes();
2998        List<CmsTabInfo> tabInfos = visitor.getTabInfos();
2999
3000        if (clientId != null) {
3001            CmsContainerElementBean containerElement = getSessionCache().getCacheContainerElement(clientId);
3002            I_CmsFormatterBean formatter = getFormatterForElement(configData, containerElement);
3003            if ((formatter != null)
3004                && formatter.isAllowsSettingsInEditor()
3005                && !formatter.getSettings(config).isEmpty()) {
3006                Map<String, CmsXmlContentProperty> settingsConfig = OpenCms.getADEManager().getFormatterSettings(
3007                    cms,
3008                    config,
3009                    formatter,
3010                    containerElement.getResource(),
3011                    locale,
3012                    getRequest());
3013                com.google.common.base.Supplier<CmsXmlContent> contentSupplier = Suppliers.memoize(() -> {
3014                    try {
3015                        return CmsXmlContentFactory.unmarshal(cms, cms.readFile(containerElement.getResource()));
3016                    } catch (CmsException e) {
3017                        LOG.error(e.getLocalizedMessage(), e);
3018                        return null;
3019                    }
3020                });
3021                settingsConfig = CmsXmlContentPropertyHelper.resolveMacrosForPropertyInfo(
3022                    cms,
3023                    null,
3024                    containerElement.getResource(),
3025                    contentSupplier,
3026                    CmsElementUtil.createStringTemplateSource(formatter, contentSupplier),
3027                    settingsConfig);
3028                CmsMessages messages = OpenCms.getWorkplaceManager().getMessages(m_workplaceLocale);
3029                List<I_CmsFormatterBean> nestedFormatters = formatter.hasNestedFormatterSettings()
3030                ? OpenCms.getADEManager().getNestedFormatters(
3031                    cms,
3032                    config,
3033                    containerElement.getResource(),
3034                    locale,
3035                    getRequest())
3036                : Collections.emptyList();
3037                String firstContentAttributeName = types.get(
3038                    entity.getTypeName()).getAttributeNames().iterator().next();
3039                List<String> addedVisibleAttrs = addSettingsAttributes(
3040                    attrConfig,
3041                    settingsConfig,
3042                    nestedFormatters,
3043                    messages,
3044                    locale,
3045                    settingPresets);
3046                addSettingsTypes(entity.getTypeName(), types, settingsConfig, nestedFormatters);
3047                if (editedLocaleEntity != null) {
3048                    transferSettingValues(editedLocaleEntity, entity);
3049                } else {
3050                    addSettingsValues(entity, containerElement, nestedFormatters);
3051                }
3052                if (tabInfos.isEmpty()) {
3053                    tabInfos.add(
3054                        new CmsTabInfo(
3055                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_CONTENT_TAB_LABEL_0),
3056                            Messages.GUI_CONTENT_TAB_LABEL_0,
3057                            Messages.GUI_CONTENT_TAB_LABEL_0,
3058                            "content",
3059                            firstContentAttributeName.substring(entity.getTypeName().length() + 1),
3060                            false,
3061                            null,
3062                            null,
3063                            null));
3064                }
3065                if (addedVisibleAttrs.size() > 0) {
3066                    tabInfos.add(
3067                        new CmsTabInfo(
3068                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_LABEL_0),
3069                            Messages.GUI_SETTINGS_TAB_LABEL_0,
3070                            Messages.GUI_SETTINGS_TAB_LABEL_0,
3071                            CmsContentDefinition.SETTINGS_TAB_ID,
3072                            CmsFileUtil.removeLeadingSeparator(addedVisibleAttrs.iterator().next()),
3073                            false,
3074                            Messages.get().getBundle(workplaceLocale).key(Messages.GUI_SETTINGS_TAB_DESCRIPTION_0),
3075                            null,
3076                            null));
3077                }
3078            }
3079
3080        }
3081
3082        CmsContentDefinition result = new CmsContentDefinition(
3083            entityId,
3084            entities,
3085            visitor.getAttributeConfigurations(),
3086            visitor.getWidgetConfigurations(),
3087            visitor.getComplexWidgetData(),
3088            visitor.getTypes(),
3089            visitor.getTabInfos(),
3090            locale.toString(),
3091            contentLocales,
3092            availableLocales,
3093            visitor.getLocaleSynchronizations(),
3094            syncValues,
3095            skipPaths,
3096            title,
3097            cms.getSitePath(file),
3098            typeName,
3099            CmsIconUtil.getIconClasses(CmsIconUtil.getDisplayType(cms, file), file.getName(), false),
3100            performedAutoCorrection,
3101            autoUnlock,
3102            getChangeHandlerScopes(content.getContentDefinition()));
3103        I_CmsContentTranslator translator = OpenCms.getWorkplaceManager().getContentTranslation();
3104        boolean translationEnabled = translator.isEnabled(cms, config, file);
3105        try {
3106            if (OpenCms.getResourceManager().getResourceType(file) instanceof CmsResourceTypeXmlAdeConfiguration) {
3107                translationEnabled = false;
3108            }
3109        } catch (Exception e) {
3110            LOG.warn(e.getLocalizedMessage(), e);
3111        }
3112        result.setTranslationEnabled(translationEnabled);
3113        return result;
3114    }
3115
3116    /**
3117     * Creates a new resource according to the new link, or returns the model file informations
3118     * modelFileId is <code>null</code> but required.<p>
3119     *
3120     * @param newLink the new link
3121     * @param referenceResource the reference resource
3122     * @param modelFileId the model file structure id
3123     * @param locale the content locale
3124     * @param mode the content creation mode
3125     * @param postCreateHandler the class name for the post-create handler
3126     * @param editHandlerData the edit handler data, in case the 'new' function is handled by an edit handler
3127     * @param settingPresets the presets for settings
3128     *
3129     * @return the content definition
3130     *
3131     * @throws CmsException if creating the resource failed
3132     */
3133    private CmsContentDefinition readContentDefinitionForNew(
3134        String newLink,
3135        CmsResource referenceResource,
3136        CmsUUID modelFileId,
3137        Locale locale,
3138        String mode,
3139        String postCreateHandler,
3140        CmsEditHandlerData editHandlerData,
3141        Map<String, String> settingPresets)
3142    throws CmsException {
3143
3144        String sitePath = getCmsObject().getSitePath(referenceResource);
3145        String resourceType;
3146        if (newLink.startsWith(CmsJspTagEdit.NEW_LINK_IDENTIFIER)) {
3147            resourceType = CmsJspTagEdit.getTypeFromNewLink(newLink);
3148        } else {
3149            resourceType = OpenCms.getResourceManager().getResourceType(referenceResource.getTypeId()).getTypeName();
3150        }
3151        String modelFile = null;
3152        if (modelFileId == null) {
3153            List<CmsResource> modelResources = CmsResourceTypeXmlContent.getModelFiles(
3154                getCmsObject(),
3155                CmsResource.getFolderPath(sitePath),
3156                resourceType);
3157            if (!modelResources.isEmpty()) {
3158                List<CmsModelResourceInfo> modelInfos = CmsContainerpageService.generateModelResourceList(
3159                    getCmsObject(),
3160                    resourceType,
3161                    modelResources,
3162                    locale);
3163                return new CmsContentDefinition(
3164                    modelInfos,
3165                    newLink,
3166                    referenceResource.getStructureId(),
3167                    locale.toString());
3168            }
3169        } else if (!modelFileId.isNullUUID()) {
3170            modelFile = getCmsObject().getSitePath(
3171                getCmsObject().readResource(modelFileId, CmsResourceFilter.IGNORE_EXPIRATION));
3172        }
3173        String newFileName = createResourceToEdit(
3174            newLink,
3175            locale,
3176            sitePath,
3177            modelFile,
3178            mode,
3179            postCreateHandler,
3180            editHandlerData);
3181        CmsResource resource = getCmsObject().readResource(newFileName, CmsResourceFilter.IGNORE_EXPIRATION);
3182        CmsFile file = getCmsObject().readFile(resource);
3183        CmsXmlContent content = getContentDocument(file, false);
3184        CmsContentDefinition contentDefinition = readContentDefinition(
3185            file,
3186            content,
3187            null,
3188            null,
3189            locale,
3190            false,
3191            null,
3192            null,
3193            settingPresets,
3194            null);
3195        contentDefinition.setDeleteOnCancel(true);
3196        return contentDefinition;
3197    }
3198
3199    /**
3200     * Stores the settings attributes to the container element bean persisted in the session cache.<p>
3201     * The container page editor will write the container page XML.<p>
3202     *
3203     * @param entity the entity
3204     * @param containerElement the container element
3205     * @param settingsConfig the settings configuration
3206     * @param nestedFormatters the nested formatters
3207     *
3208     * @return <code>true</code> in case any changed settings where saved
3209     */
3210    @SuppressWarnings("null")
3211    private boolean saveSettings(
3212        CmsEntity entity,
3213        CmsContainerElementBean containerElement,
3214        Map<String, CmsXmlContentProperty> settingsConfig,
3215        List<I_CmsFormatterBean> nestedFormatters) {
3216
3217        boolean hasChangedSettings = false;
3218        Map<String, String> values = new HashMap<>(containerElement.getIndividualSettings());
3219        for (Entry<String, CmsXmlContentProperty> settingsEntry : settingsConfig.entrySet()) {
3220            String value = null;
3221            boolean nested = false;
3222            if (nestedFormatters != null) {
3223                for (I_CmsFormatterBean formatter : nestedFormatters) {
3224                    if (settingsEntry.getKey().startsWith(formatter.getId())) {
3225
3226                        CmsEntity nestedEntity = null;
3227                        CmsEntityAttribute attribute = entity.getAttribute(getSettingsAttributeName(formatter.getId()));
3228                        if (attribute != null) {
3229                            nestedEntity = attribute.getComplexValue();
3230                            CmsEntityAttribute valueAttribute = nestedEntity.getAttribute(
3231                                getSettingsAttributeName(settingsEntry.getKey()));
3232                            if (valueAttribute != null) {
3233                                value = valueAttribute.getSimpleValue();
3234                            }
3235                        }
3236                        nested = true;
3237                        break;
3238                    }
3239                }
3240            }
3241            if (!nested) {
3242                CmsEntityAttribute valueAttribute = entity.getAttribute(
3243                    getSettingsAttributeName(settingsEntry.getKey()));
3244                if (valueAttribute != null) {
3245                    value = valueAttribute.getSimpleValue();
3246                }
3247            }
3248            if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)
3249                && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget())
3250                && values.containsKey(settingsEntry.getKey())) {
3251                values.remove(settingsEntry.getKey());
3252                hasChangedSettings = true;
3253            } else if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)
3254                && !CmsGwtConstants.HIDDEN_SETTINGS_WIDGET_NAME.equals(settingsEntry.getValue().getWidget())
3255                && !value.equals(values.get(settingsEntry.getKey()))) {
3256                values.put(settingsEntry.getKey(), value);
3257                hasChangedSettings = true;
3258            }
3259        }
3260        if (hasChangedSettings) {
3261            containerElement.updateIndividualSettings(values);
3262            getSessionCache().setCacheContainerElement(containerElement.editorHash(), containerElement);
3263        }
3264        return hasChangedSettings;
3265    }
3266
3267    /**
3268     * Synchronizes the locale independent fields for the given entity.<p>
3269     *
3270     * @param file the content file
3271     * @param content the XML content
3272     * @param skipPaths the paths to skip during locale synchronization
3273     * @param entity the entity
3274     *
3275     * @throws CmsXmlException if something goes wrong
3276     */
3277    private void synchronizeLocaleIndependentForEntity(
3278        CmsFile file,
3279        CmsXmlContent content,
3280        Collection<String> skipPaths,
3281        CmsEntity entity)
3282    throws CmsXmlException {
3283
3284        CmsObject cms = getCmsObject();
3285        String entityId = entity.getId();
3286        Locale contentLocale = CmsLocaleManager.getLocale(CmsContentDefinition.getLocaleFromId(entityId));
3287        CmsContentTypeVisitor visitor = null;
3288        CmsEntity originalEntity = null;
3289        if (content.getHandler().hasVisibilityHandlers()) {
3290            visitor = new CmsContentTypeVisitor(cms, file, contentLocale);
3291            visitor.visitTypes(content.getContentDefinition(), getWorkplaceLocale(cms));
3292        }
3293        if (content.hasLocale(contentLocale)) {
3294            if ((visitor != null) && visitor.hasInvisibleFields()) {
3295                // we need to add invisible content values to the entity before saving
3296                Element element = content.getLocaleNode(contentLocale);
3297                originalEntity = readEntity(
3298                    content,
3299                    element,
3300                    contentLocale,
3301                    entityId,
3302                    "",
3303                    getTypeUri(content.getContentDefinition()),
3304                    visitor,
3305                    true,
3306                    entity);
3307            }
3308            content.removeLocale(contentLocale);
3309        }
3310        content.addLocale(cms, contentLocale);
3311        if ((visitor != null) && visitor.hasInvisibleFields()) {
3312            transferInvisibleValues(originalEntity, entity, visitor);
3313        }
3314        addEntityAttributes(cms, content, "", entity, contentLocale);
3315        content.synchronizeLocaleIndependentValues(cms, skipPaths, contentLocale);
3316    }
3317
3318    /**
3319     * Transfers settings attribute values from one entity to another.<p>
3320     *
3321     * @param source the source entity
3322     * @param target the target entity
3323     */
3324    private void transferSettingValues(CmsEntity source, CmsEntity target) {
3325
3326        for (CmsEntityAttribute attr : source.getAttributes()) {
3327            if (isSettingsAttribute(attr.getAttributeName())) {
3328                if (attr.isSimpleValue()) {
3329                    target.addAttributeValue(attr.getAttributeName(), attr.getSimpleValue());
3330                } else {
3331                    CmsEntity nestedSource = attr.getComplexValue();
3332
3333                    CmsEntity nested = new CmsEntity(nestedSource.getId(), nestedSource.getTypeName());
3334                    for (CmsEntityAttribute nestedAttr : nestedSource.getAttributes()) {
3335                        nested.addAttributeValue(nestedAttr.getAttributeName(), nestedAttr.getSimpleValue());
3336                    }
3337                    target.addAttributeValue(attr.getAttributeName(), nested);
3338                }
3339            }
3340        }
3341    }
3342
3343    /**
3344     * Validates the given XML content.<p>
3345     *
3346     * @param cms the cms context
3347     * @param structureId the structure id
3348     * @param content the XML content
3349     *
3350     * @return the validation result
3351     */
3352    private CmsValidationResult validateContent(CmsObject cms, CmsUUID structureId, CmsXmlContent content) {
3353
3354        return validateContent(cms, structureId, content, null);
3355    }
3356
3357    /**
3358     * Validates the given XML content.<p>
3359     *
3360     * @param cms the cms context
3361     * @param structureId the structure id
3362     * @param content the XML content
3363     * @param fieldNames if not null, only validation errors in paths from this set will be added to the validation result
3364     *
3365     * @return the validation result
3366     */
3367    private CmsValidationResult validateContent(
3368        CmsObject cms,
3369        CmsUUID structureId,
3370        CmsXmlContent content,
3371        Set<String> fieldNames) {
3372
3373        CmsXmlContentErrorHandler errorHandler = content.validate(cms);
3374        Map<String, Map<String[], CmsPair<String, String>>> errorsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>();
3375
3376        if (errorHandler.hasErrors()) {
3377            boolean reallyHasErrors = false;
3378            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getErrors().entrySet()) {
3379                Map<String[], CmsPair<String, String>> errors = new HashMap<String[], CmsPair<String, String>>();
3380                for (Entry<String, String> error : localeEntry.getValue().entrySet()) {
3381                    I_CmsXmlContentValue value = content.getValue(error.getKey(), localeEntry.getKey());
3382                    if (content.getHandler().isVisible(
3383                        cms,
3384                        value,
3385                        CmsXmlUtils.removeAllXpathIndices(value.getPath()),
3386                        content.getFile(),
3387                        localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) {
3388                        errors.put(
3389                            getPathElements(content, value),
3390                            new CmsPair<String, String>(error.getValue(), error.getKey()));
3391                        reallyHasErrors = true;
3392                    }
3393
3394                }
3395                if (reallyHasErrors) {
3396                    errorsByEntity.put(
3397                        CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
3398                        errors);
3399                }
3400            }
3401        }
3402        Map<String, Map<String[], CmsPair<String, String>>> warningsByEntity = new HashMap<String, Map<String[], CmsPair<String, String>>>();
3403        if (errorHandler.hasWarnings()) {
3404            boolean reallyHasErrors = false;
3405            for (Entry<Locale, Map<String, String>> localeEntry : errorHandler.getWarnings().entrySet()) {
3406                Map<String[], CmsPair<String, String>> warnings = new HashMap<String[], CmsPair<String, String>>();
3407                for (Entry<String, String> warning : localeEntry.getValue().entrySet()) {
3408                    I_CmsXmlContentValue value = content.getValue(warning.getKey(), localeEntry.getKey());
3409                    if (content.getHandler().isVisible(
3410                        cms,
3411                        value,
3412                        CmsXmlUtils.removeAllXpathIndices(value.getPath()),
3413                        content.getFile(),
3414                        localeEntry.getKey()) && ((fieldNames == null) || fieldNames.contains(value.getPath()))) {
3415                        warnings.put(
3416                            getPathElements(content, value),
3417                            new CmsPair<String, String>(warning.getValue(), warning.getKey()));
3418                        reallyHasErrors = true;
3419                    }
3420                }
3421                if (reallyHasErrors) {
3422                    warningsByEntity.put(
3423                        CmsContentDefinition.uuidToEntityId(structureId, localeEntry.getKey().toString()),
3424                        warnings);
3425                }
3426            }
3427        }
3428        return new CmsValidationResult(errorsByEntity, warningsByEntity);
3429    }
3430
3431    /**
3432     * Validates the settings attribute values.<p>
3433     *
3434     * @param entity the entity to validate
3435     * @param validationResult the validation result
3436     * @param settingsConfig the settings configuration
3437     */
3438    private void validateSettings(
3439        CmsEntity entity,
3440        CmsValidationResult validationResult,
3441        Map<String, CmsXmlContentProperty> settingsConfig) {
3442
3443        Map<String, Map<String[], CmsPair<String, String>>> errors = validationResult.getErrors();
3444        Map<String[], CmsPair<String, String>> entityErrors = errors.get(entity.getId());
3445        if (entityErrors == null) {
3446            entityErrors = new HashMap<String[], CmsPair<String, String>>();
3447        }
3448        Map<String, Map<String[], CmsPair<String, String>>> warnings = validationResult.getWarnings();
3449        Map<String[], CmsPair<String, String>> entityWarnings = warnings.get(entity.getId());
3450        if (entityWarnings == null) {
3451            entityWarnings = new HashMap<String[], CmsPair<String, String>>();
3452        }
3453
3454        for (CmsEntityAttribute attribute : entity.getAttributes()) {
3455            if (isSettingsAttribute(attribute.getAttributeName())) {
3456                if (attribute.isSimpleValue()) {
3457                    String settingsKey = attribute.getAttributeName().substring(
3458                        SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1);
3459                    CmsXmlContentProperty prop = settingsConfig.get(settingsKey);
3460                    if (prop != null) {
3461                        String regex = prop.getRuleRegex();
3462                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) {
3463                            if (!attribute.getSimpleValue().matches(regex)) {
3464                                String[] path = new String[] {attribute.getAttributeName()};
3465
3466                                if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) {
3467                                    entityErrors.put(
3468                                        path,
3469                                        new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
3470                                } else {
3471                                    entityWarnings.put(
3472                                        path,
3473                                        new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
3474                                }
3475                            }
3476                        }
3477                    }
3478                } else {
3479                    CmsEntity nested = attribute.getComplexValue();
3480                    for (CmsEntityAttribute nestedAttribute : nested.getAttributes()) {
3481                        String settingsKey = nestedAttribute.getAttributeName().substring(
3482                            SETTINGS_ATTRIBUTE_NAME_PREFIX.length() + 1);
3483                        CmsXmlContentProperty prop = settingsConfig.get(settingsKey);
3484                        if (prop != null) {
3485                            String regex = prop.getRuleRegex();
3486                            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(regex)) {
3487                                if (!nestedAttribute.getSimpleValue().matches(regex)) {
3488                                    String[] path = new String[] {
3489                                        attribute.getAttributeName(),
3490                                        nestedAttribute.getAttributeName()};
3491                                    if (SETTINGS_RULE_TYPE_ERROR.equals(prop.getRuleType())) {
3492                                        entityErrors.put(
3493                                            path,
3494                                            new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
3495                                    } else {
3496                                        entityWarnings.put(
3497                                            path,
3498                                            new CmsPair<String, String>(prop.getError(), prop.getNiceName()));
3499                                    }
3500                                }
3501                            }
3502                        }
3503                    }
3504                }
3505            }
3506        }
3507        if (!entityErrors.isEmpty()) {
3508            errors.put(entity.getId(), entityErrors);
3509        }
3510        if (!entityWarnings.isEmpty()) {
3511            warnings.put(entity.getId(), entityWarnings);
3512        }
3513
3514    }
3515
3516    /**
3517     * Writes the categories that are dynamically read/wrote by the content editor.
3518     *
3519     * @param file the file where the content is stored.
3520     * @param content the content.
3521     * @param lastEditedEntity the last edited entity
3522     */
3523    private void writeCategories(CmsFile file, CmsXmlContent content, CmsEntity lastEditedEntity) {
3524
3525        // do nothing if one of the arguments is empty.
3526        if ((null == content) || (null == file)) {
3527            return;
3528        }
3529
3530        CmsObject cms = getCmsObject();
3531        if (!content.getLocales().isEmpty()) {
3532            Locale locale = content.getLocales().iterator().next();
3533            CmsEntity entity = lastEditedEntity;
3534            List<I_CmsXmlContentValue> values = content.getValues(locale);
3535            for (I_CmsXmlContentValue value : values) {
3536                if (value.getTypeName().equals(CmsXmlDynamicCategoryValue.TYPE_NAME)) {
3537                    I_CmsWidget widget = CmsWidgetUtil.collectWidgetInfo(cms, value).getWidget();
3538                    List<CmsCategory> categories = new ArrayList<CmsCategory>(0);
3539                    try {
3540                        categories = CmsCategoryService.getInstance().readResourceCategories(cms, file);
3541                    } catch (CmsException e) {
3542                        LOG.error(Messages.get().getBundle().key(Messages.ERROR_FAILED_READING_CATEGORIES_1), e);
3543                    }
3544                    if ((null != widget) && (widget instanceof CmsCategoryWidget)) {
3545                        String mainCategoryPath = ((CmsCategoryWidget)widget).getStartingCategory(
3546                            cms,
3547                            cms.getSitePath(file));
3548                        for (CmsCategory category : categories) {
3549                            if (category.getPath().startsWith(mainCategoryPath)) {
3550                                try {
3551                                    CmsCategoryService.getInstance().removeResourceFromCategory(
3552                                        cms,
3553                                        cms.getSitePath(file),
3554                                        category);
3555                                } catch (CmsException e) {
3556                                    LOG.error(e.getLocalizedMessage(), e);
3557                                }
3558                            }
3559                        }
3560                        if (null == entity) {
3561                            try {
3562                                CmsContentDefinition definition = readContentDefinition(
3563                                    file,
3564                                    content,
3565                                    "dummy",
3566                                    null,
3567                                    locale,
3568                                    false,
3569                                    null,
3570                                    null,
3571                                    Collections.emptyMap(),
3572                                    null);
3573                                entity = definition.getEntity();
3574                            } catch (CmsException e) {
3575                                LOG.error(e.getLocalizedMessage(), e);
3576                            }
3577                        }
3578                        String checkedCategories = "";
3579                        if (null != entity) {
3580                            checkedCategories = CmsEntity.getValueForPath(entity, value.getPath().split("/"));
3581                        }
3582                        List<String> checkedCategoryList = Arrays.asList(checkedCategories.split(","));
3583                        for (String category : checkedCategoryList) {
3584                            try {
3585                                CmsCategoryService.getInstance().addResourceToCategory(
3586                                    cms,
3587                                    cms.getSitePath(file),
3588                                    CmsCategoryService.getInstance().getCategory(cms, category));
3589                            } catch (CmsException e) {
3590                                if (LOG.isWarnEnabled()) {
3591                                    LOG.warn(e.getLocalizedMessage(), e);
3592                                }
3593                            }
3594                        }
3595                    }
3596                }
3597            }
3598        }
3599    }
3600
3601    /**
3602     * Writes the xml content to the vfs and re-initializes the member variables.<p>
3603     *
3604     * @param cms the cms context
3605     * @param file the file to write to
3606     * @param content the content
3607     * @param encoding the file encoding
3608     *
3609     * @return the content
3610     *
3611     * @throws CmsException if writing the file fails
3612     */
3613    private CmsXmlContent writeContent(CmsObject cms, CmsFile file, CmsXmlContent content, String encoding)
3614    throws CmsException {
3615
3616        String decodedContent = content.toString();
3617        try {
3618            file.setContents(decodedContent.getBytes(encoding));
3619        } catch (UnsupportedEncodingException e) {
3620            throw new CmsException(
3621                org.opencms.workplace.editors.Messages.get().container(
3622                    org.opencms.workplace.editors.Messages.ERR_INVALID_CONTENT_ENC_1,
3623                    file.getRootPath()),
3624                e);
3625        }
3626        // the file content might have been modified during the write operation
3627
3628        cms.getRequestContext().setAttribute(ATTR_EDITOR_SAVING, "true");
3629        try {
3630            file = cms.writeFile(file);
3631        } finally {
3632            cms.getRequestContext().removeAttribute(ATTR_EDITOR_SAVING);
3633        }
3634        return CmsXmlContentFactory.unmarshal(cms, file);
3635    }
3636
3637}