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.shared;
029
030import org.opencms.acacia.shared.CmsAttributeConfiguration;
031import org.opencms.acacia.shared.CmsEntity;
032import org.opencms.acacia.shared.CmsEntityAttribute;
033import org.opencms.acacia.shared.CmsTabInfo;
034import org.opencms.acacia.shared.CmsType;
035import org.opencms.gwt.shared.CmsModelResourceInfo;
036import org.opencms.util.CmsUUID;
037
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044/**
045 * Contains all information needed for editing an XMLContent.<p>
046 */
047public class CmsContentDefinition extends org.opencms.acacia.shared.CmsContentDefinition {
048
049    /** The id of the element settings tab. */
050    public static final String SETTINGS_TAB_ID = "###formattersettings###";
051
052    /** The entity id prefix. */
053    private static final String ENTITY_ID_PREFIX = "http://opencms.org/resources/";
054
055    /** The value of the acacia-unlock configuration option. */
056    private boolean m_autoUnlock;
057
058    /** The available locales. */
059    private Map<String, String> m_availableLocales;
060
061    /** A map from attribute names to complex widget configurations. */
062    private Map<String, CmsComplexWidgetData> m_complexWidgetData;
063
064    /** The content locales. */
065    private List<String> m_contentLocales;
066
067    /** Flag indicating the resource needs to removed on cancel. */
068    private boolean m_deleteOnCancel;
069
070    /** Indicates that editor change handlers are configured. */
071    private Set<String> m_editorChangeScopes;
072
073    /** The external widget configurations. */
074    private List<CmsExternalWidgetConfiguration> m_externalWidgetConfigurations;
075
076    /** The resource icon classes. */
077    private String m_iconClasses;
078
079    /** The direct edit flag (set to true for classic direct edit mode). */
080    private boolean m_isDirectEdit;
081
082    /** The model file informations. */
083    private List<CmsModelResourceInfo> m_modelInfos;
084
085    /** The new link. */
086    private String m_newLink;
087
088    /** Flag indicating the current content has an invalid XML structure and was auto corrected. */
089    private boolean m_performedAutocorrection;
090
091    /** The reference resource structure id. */
092    private CmsUUID m_referenceResourceId;
093
094    /** The resource type name. */
095    private String m_resourceType;
096
097    /** True if the element should be marked as 'reused' in the content editor. */
098    private boolean m_reusedElement;
099
100    /** The site path. */
101    private String m_sitePath;
102
103    /** The paths to skip during locale synchronization. */
104    private Collection<String> m_skipPaths;
105
106    /** The elements that require a synchronization across all locales. */
107    private List<String> m_synchronizations;
108
109    /** The locale synchronization values. */
110    private Map<String, String> m_syncValues;
111
112    /** The content title. */
113    private String m_title;
114
115    /**
116     * Constructor for model file informations object.<p>
117     *
118     * @param modelInfos the model file informations
119     * @param newLink the new link
120     * @param referenceId the reference resource structure id
121     * @param locale the locale
122     */
123    public CmsContentDefinition(
124        List<CmsModelResourceInfo> modelInfos,
125        String newLink,
126        CmsUUID referenceId,
127        String locale) {
128
129        super(null, null, null, null, null, true, locale);
130        m_modelInfos = modelInfos;
131        m_newLink = newLink;
132        m_referenceResourceId = referenceId;
133    }
134
135    /**
136     * Constructor.<p>
137     *
138     * @param entityId the entity id
139     * @param entities the locale specific entities of the content
140     * @param configurations the attribute configurations
141     * @param externalWidgetConfigurations the external widget configurations
142     * @param complexWidgetData the complex widget configurations
143     * @param types the types
144     * @param tabInfos the tab information
145     * @param locale the content locale
146     * @param contentLocales the content locales
147     * @param availableLocales the available locales
148     * @param synchronizations the elements that require a synchronization across all locales
149     * @param syncValues the locale synchronization values
150     * @param skipPaths the paths to skip during locale synchronization
151     * @param title the content title
152     * @param sitePath the site path
153     * @param resourceType the resource type name
154     * @param iconClasses the resource icon classes
155     * @param performedAutocorrection flag indicating the current content has an invalid XML structure and was auto corrected
156     * @param autoUnlock false if the editor should not unlock resources automatically in standalone mode
157     * @param editorChangeScopes the editor change handler scopes
158     */
159    public CmsContentDefinition(
160        String entityId,
161        Map<String, CmsEntity> entities,
162        Map<String, CmsAttributeConfiguration> configurations,
163        Collection<CmsExternalWidgetConfiguration> externalWidgetConfigurations,
164        Map<String, CmsComplexWidgetData> complexWidgetData,
165        Map<String, CmsType> types,
166        List<CmsTabInfo> tabInfos,
167        String locale,
168        List<String> contentLocales,
169        Map<String, String> availableLocales,
170        List<String> synchronizations,
171        Map<String, String> syncValues,
172        Collection<String> skipPaths,
173        String title,
174        String sitePath,
175        String resourceType,
176        String iconClasses,
177        boolean performedAutocorrection,
178        boolean autoUnlock,
179        Set<String> editorChangeScopes) {
180
181        super(entityId, entities, configurations, types, tabInfos, true, locale);
182        m_contentLocales = contentLocales;
183        m_availableLocales = availableLocales;
184        m_synchronizations = synchronizations;
185        m_syncValues = syncValues;
186        m_skipPaths = skipPaths;
187        m_complexWidgetData = complexWidgetData;
188        m_title = title;
189        m_sitePath = sitePath;
190        m_resourceType = resourceType;
191        m_iconClasses = iconClasses;
192        m_externalWidgetConfigurations = new ArrayList<CmsExternalWidgetConfiguration>(externalWidgetConfigurations);
193        m_performedAutocorrection = performedAutocorrection;
194        m_autoUnlock = autoUnlock;
195        m_editorChangeScopes = editorChangeScopes;
196    }
197
198    /**
199     * Constructor for serialization only.<p>
200     */
201    protected CmsContentDefinition() {
202
203        super();
204    }
205
206    /**
207     * Returns the UUID according to the given entity id.<p>
208     *
209     * @param entityId the entity id
210     *
211     * @return the entity id
212     */
213    public static CmsUUID entityIdToUuid(String entityId) {
214
215        if (entityId.startsWith(ENTITY_ID_PREFIX)) {
216            entityId = entityId.substring(entityId.lastIndexOf("/") + 1);
217        }
218        return new CmsUUID(entityId);
219    }
220
221    /**
222     * Extracts the locale from the entity id.<p>
223     *
224     * @param entityId the entity id
225     *
226     * @return the locale
227     */
228    public static String getLocaleFromId(String entityId) {
229
230        if (entityId.startsWith(ENTITY_ID_PREFIX)) {
231            return entityId.substring(ENTITY_ID_PREFIX.length(), entityId.lastIndexOf("/"));
232        }
233        return null;
234    }
235
236    /**
237     * Returns the value for the given XPath expression.<p>
238     *
239     * @param entity the entity
240     * @param path the path
241     *
242     * @return the value
243     */
244    public static String getValueForPath(CmsEntity entity, String path) {
245
246        String result = null;
247        if (path.startsWith("/")) {
248            path = path.substring(1);
249        }
250        String attributeName;
251        if (path.contains("/")) {
252            attributeName = path.substring(0, path.indexOf("/"));
253            path = path.substring(path.indexOf("/"));
254        } else {
255            attributeName = path;
256            path = null;
257        }
258        int index = org.opencms.acacia.shared.CmsContentDefinition.extractIndex(attributeName);
259        if (index > 0) {
260            index--;
261
262        }
263        String typeName = entity.getTypeName();
264
265        attributeName = typeName + "/" + org.opencms.acacia.shared.CmsContentDefinition.removeIndex(attributeName);
266        CmsEntityAttribute choiceAttr = entity.getAttribute(CmsType.CHOICE_ATTRIBUTE_NAME);
267        if ((choiceAttr != null) && choiceAttr.isComplexValue()) {
268            List<CmsEntity> choiceChildren = new ArrayList<>();
269            for (CmsEntity child : choiceAttr.getComplexValues()) {
270                if (child.getAttribute(attributeName) != null) {
271                    choiceChildren.add(child);
272                }
273            }
274            if (index < choiceChildren.size()) {
275                entity = choiceChildren.get(index);
276                index = 0;
277            } else {
278                return null;
279            }
280        }
281
282        CmsEntityAttribute attribute = entity.getAttribute(attributeName);
283        if (!((attribute == null) || (attribute.isComplexValue() && (path == null)))) {
284            if (attribute.isSimpleValue()) {
285                if ((path == null) && (attribute.getValueCount() > 0)) {
286                    List<String> values = attribute.getSimpleValues();
287                    result = values.get(index);
288                }
289            } else if (attribute.getValueCount() > (index)) {
290                List<CmsEntity> values = attribute.getComplexValues();
291                result = getValueForPath(values.get(index), path);
292            }
293        }
294        return result;
295    }
296
297    /**
298     * Transfers values from the original entity to the given target entity.<p>
299     *
300     * @param original the original entity
301     * @param target the target entity
302     * @param transferAttributes the attributes to consider for the value transfer
303     * @param entityTypes the entity types
304     * @param attributeConfigurations the attribute configurations
305     * @param considerDefaults if default values should be added according to minimum occurrence settings
306     */
307    public static void transferValues(
308        CmsEntity original,
309        CmsEntity target,
310        List<String> transferAttributes,
311        Map<String, CmsType> entityTypes,
312        Map<String, CmsAttributeConfiguration> attributeConfigurations,
313        boolean considerDefaults) {
314
315        CmsType entityType = entityTypes.get(target.getTypeName());
316        for (String attributeName : entityType.getAttributeNames()) {
317            CmsType attributeType = entityTypes.get(entityType.getAttributeTypeName(attributeName));
318            if (transferAttributes.contains(attributeName)) {
319
320                target.removeAttribute(attributeName);
321                CmsEntityAttribute attribute = original != null ? original.getAttribute(attributeName) : null;
322                if (attribute != null) {
323                    if (attributeType.isSimpleType()) {
324                        for (String value : attribute.getSimpleValues()) {
325                            target.addAttributeValue(attributeName, value);
326                        }
327                        if (considerDefaults) {
328                            for (int i = attribute.getValueCount(); i < entityType.getAttributeMinOccurrence(
329                                attributeName); i++) {
330                                target.addAttributeValue(
331                                    attributeName,
332                                    attributeConfigurations.get(attributeName).getDefaultValue());
333                            }
334                        }
335                    } else {
336                        for (CmsEntity value : attribute.getComplexValues()) {
337                            target.addAttributeValue(attributeName, value);
338                        }
339                        if (considerDefaults) {
340                            for (int i = attribute.getValueCount(); i < entityType.getAttributeMinOccurrence(
341                                attributeName); i++) {
342                                target.addAttributeValue(
343                                    attributeName,
344                                    createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
345                            }
346                        }
347                    }
348                } else if (considerDefaults) {
349                    for (int i = 0; i < entityType.getAttributeMinOccurrence(attributeName); i++) {
350                        if (attributeType.isSimpleType()) {
351                            target.addAttributeValue(
352                                attributeName,
353                                attributeConfigurations.get(attributeName).getDefaultValue());
354                        } else {
355                            target.addAttributeValue(
356                                attributeName,
357                                createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
358                        }
359                    }
360                }
361            } else {
362                if (!attributeType.isSimpleType()) {
363                    CmsEntityAttribute targetAttribute = target.getAttribute(attributeName);
364                    CmsEntityAttribute originalAttribute = original != null
365                    ? original.getAttribute(attributeName)
366                    : null;
367                    if (targetAttribute != null) {
368                        for (int i = 0; i < targetAttribute.getComplexValues().size(); i++) {
369                            CmsEntity subTarget = targetAttribute.getComplexValues().get(i);
370                            CmsEntity subOriginal = (originalAttribute != null)
371                                && (originalAttribute.getComplexValues().size() > i)
372                                ? originalAttribute.getComplexValues().get(i)
373                                : null;
374                            transferValues(
375                                subOriginal,
376                                subTarget,
377                                transferAttributes,
378                                entityTypes,
379                                attributeConfigurations,
380                                considerDefaults);
381                        }
382                    }
383                }
384            }
385        }
386    }
387
388    /**
389     * Returns the entity id according to the given UUID.<p>
390     *
391     * @param uuid the UUID
392     * @param locale the content locale
393     *
394     * @return the entity id
395     */
396    public static String uuidToEntityId(CmsUUID uuid, String locale) {
397
398        return ENTITY_ID_PREFIX + locale + "/" + uuid.toString();
399    }
400
401    /**
402     * Creates an entity object containing the default values configured for it's type.<p>
403     *
404     * @param entityType the entity type
405     * @param entityTypes the entity types
406     * @param attributeConfigurations the attribute configurations
407     *
408     * @return the created entity
409     */
410    protected static CmsEntity createDefaultValueEntity(
411        CmsType entityType,
412        Map<String, CmsType> entityTypes,
413        Map<String, CmsAttributeConfiguration> attributeConfigurations) {
414
415        CmsEntity result = new CmsEntity(null, entityType.getId());
416        for (String attributeName : entityType.getAttributeNames()) {
417            CmsType attributeType = entityTypes.get(entityType.getAttributeTypeName(attributeName));
418            for (int i = 0; i < entityType.getAttributeMinOccurrence(attributeName); i++) {
419                if (attributeType.isSimpleType()) {
420                    result.addAttributeValue(
421                        attributeName,
422                        attributeConfigurations.get(attributeName).getDefaultValue());
423                } else {
424                    result.addAttributeValue(
425                        attributeName,
426                        createDefaultValueEntity(attributeType, entityTypes, attributeConfigurations));
427                }
428            }
429        }
430        return result;
431    }
432
433    /**
434     * Returns the available locales.<p>
435     *
436     * @return the available locales
437     */
438    public Map<String, String> getAvailableLocales() {
439
440        return m_availableLocales;
441    }
442
443    /**
444     * Gets the complex widget configurations.<p>
445     *
446     * @return the complex widget configurations
447     */
448    public Map<String, CmsComplexWidgetData> getComplexWidgetData() {
449
450        return m_complexWidgetData;
451    }
452
453    /**
454     * Returns the content locales.<p>
455     *
456     * @return the content locales
457     */
458    public List<String> getContentLocales() {
459
460        return m_contentLocales;
461    }
462
463    /**
464     * Returns the editor change handler scopes.<p>
465     *
466     * @return the editor change handler scopes
467     */
468    public Set<String> getEditorChangeScopes() {
469
470        return m_editorChangeScopes;
471    }
472
473    /**
474     * Returns the external widget configurations.<p>
475     *
476     * @return the external widget configurations
477     */
478    public List<CmsExternalWidgetConfiguration> getExternalWidgetConfigurations() {
479
480        return m_externalWidgetConfigurations;
481    }
482
483    /**
484     * Returns the resource icon classes.<p>
485     *
486     * @return the resource icon classes
487     */
488    public String getIconClasses() {
489
490        return m_iconClasses;
491    }
492
493    /**
494     * Returns the model file informations.<p>
495     *
496     * @return the model file informations
497     */
498    public List<CmsModelResourceInfo> getModelInfos() {
499
500        return m_modelInfos;
501    }
502
503    /**
504     * Returns the new link.<p>
505     *
506     * @return the new link
507     */
508    public String getNewLink() {
509
510        return m_newLink;
511    }
512
513    /**
514     * Returns the reference resource structure id.<p>
515     *
516     * @return the reference resource structure id
517     */
518    public CmsUUID getReferenceResourceId() {
519
520        return m_referenceResourceId;
521    }
522
523    /**
524     * Returns the resource type.<p>
525     *
526     * @return the resource type
527     */
528    public String getResourceType() {
529
530        return m_resourceType;
531    }
532
533    /**
534     * Returns the site path.<p>
535     *
536     * @return the site path
537     */
538    public String getSitePath() {
539
540        return m_sitePath;
541    }
542
543    /**
544     * Returns the paths to skip during locale synchronization.<p>
545     *
546     * @return the paths to skip during locale synchronization
547     */
548    public Collection<String> getSkipPaths() {
549
550        return m_skipPaths;
551    }
552
553    /**
554     * Returns the elements that require a synchronization across all locales.<p>
555     *
556     * @return the element paths
557     */
558    public List<String> getSynchronizations() {
559
560        return m_synchronizations;
561    }
562
563    /**
564     * Returns the locale synchronization values.<p>
565     *
566     * @return the locale synchronization values
567     */
568    public Map<String, String> getSyncValues() {
569
570        return m_syncValues;
571    }
572
573    /**
574     * Returns the title.<p>
575     *
576     * @return the title
577     */
578    public String getTitle() {
579
580        return m_title;
581    }
582
583    /**
584     * Returns <code>true</code> if any editor change handlers have been configured for this content type.<p>
585     *
586     * @return <code>true</code> if any editor change handlers have been configured for this content type.<p>
587     */
588    public boolean hasEditorChangeHandlers() {
589
590        return (m_editorChangeScopes != null) && !m_editorChangeScopes.isEmpty();
591    }
592
593    /**
594     * Returns if there are locale synchronized elements configured.<p>
595     *
596     * @return <code>true</code> if there are locale synchronized elements configured
597     */
598    public boolean hasSynchronizedElements() {
599
600        return !m_synchronizations.isEmpty();
601    }
602
603    /**
604     * Returns the value of the acacia-unlock configuration option.<p>
605     *
606     * @return the value of the acacia-unlock configuration option
607     */
608    public boolean isAutoUnlock() {
609
610        return m_autoUnlock;
611    }
612
613    /**
614     * Returns if the resource needs to removed on cancel.<p>
615     *
616     * @return <code>true</code> if the resource needs to removed on cancel
617     */
618    public boolean isDeleteOnCancel() {
619
620        return m_deleteOnCancel;
621    }
622
623    /**
624     * Returns true if the direct edit flag is set, which means that the editor was opened from the classic direct edit mode.<p>
625     *
626     * @return true if the direct edit flag is set
627     */
628    public boolean isDirectEdit() {
629
630        return m_isDirectEdit;
631    }
632
633    /**
634     * Returns if the model file informations are present, in this case no additional data is contained.<p>
635     *
636     * @return <code>true</code> if the definition contains the model file informations
637     */
638    public boolean isModelInfo() {
639
640        return m_modelInfos != null;
641    }
642
643    /**
644     * Returns if auto correction was performed.<p>
645     *
646     * @return <code>true</code> if auto correction was performed
647     */
648    public boolean isPerformedAutocorrection() {
649
650        return m_performedAutocorrection;
651    }
652
653    /**
654     * Checks if the element should be marked as 'reused' in the editor.
655     *
656     * @return true if the element should be marked as 'reused' in the editor
657     */
658    public boolean isReusedElement() {
659
660        return m_reusedElement;
661    }
662
663    /**
664     * Sets if the resource needs to removed on cancel.<p>
665     *
666     * @param deleteOnCancel <code>true</code> if the resource needs to removed on cancel
667     */
668    public void setDeleteOnCancel(boolean deleteOnCancel) {
669
670        m_deleteOnCancel = deleteOnCancel;
671    }
672
673    /**
674     * Sets the value of the direct edit flag.<p>
675     *
676     * @param isDirectEdit the new value for the direct edit flag
677     */
678    public void setDirectEdit(boolean isDirectEdit) {
679
680        m_isDirectEdit = isDirectEdit;
681    }
682
683    /**
684     * Enables / disables marking of the element as 'reused' in the content editor.
685     *
686     * @param reused true if the element should be shown as 'reused'
687     */
688    public void setReusedElement(boolean reused) {
689
690        m_reusedElement = reused;
691
692    }
693}