001/*
002 *  Copyright 2017 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.contenttypeseditor;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030
031import org.ametys.cms.contenttype.AbstractMetadataSetElement;
032import org.ametys.cms.contenttype.ContentType;
033import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
034import org.ametys.cms.contenttype.Fieldset;
035import org.ametys.cms.contenttype.MetadataDefinition;
036import org.ametys.cms.contenttype.MetadataDefinitionReference;
037import org.ametys.cms.contenttype.MetadataSet;
038import org.ametys.cms.contenttype.RepeaterDefinition;
039import org.ametys.cms.contenttype.RichTextMetadataDefinition;
040import org.ametys.cms.contenttype.SemanticAnnotation;
041import org.ametys.cms.contenttype.indexing.CustomIndexingField;
042import org.ametys.cms.contenttype.indexing.IndexingField;
043import org.ametys.cms.contenttype.indexing.IndexingModel;
044import org.ametys.cms.contenttype.indexing.MetadataIndexingField;
045import org.ametys.core.right.Right;
046import org.ametys.core.right.RightsExtensionPoint;
047import org.ametys.core.util.I18nUtils;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.parameter.DefaultValidator;
050import org.ametys.runtime.parameter.Enumerator;
051import org.ametys.runtime.parameter.StaticEnumerator;
052import org.ametys.runtime.parameter.Validator;
053import org.ametys.runtime.plugin.component.PluginAware;
054
055/**
056 * Helper to retrieve content type infos
057 */
058public class ContentTypeInformationsHelper extends AbstractLogEnabled implements Component, Serviceable, PluginAware
059{
060    /** The Avalon role name */
061    public static final String ROLE = ContentTypeInformationsHelper.class.getName();
062    
063    /** The content type extension point instance */
064    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
065    
066    /** The rights extension point instance */
067    protected RightsExtensionPoint _rightsExtensionPoint;
068    
069    /** Utility methods helping the management of internationalizable text */
070    protected I18nUtils _i18nUtils;
071    
072    private Map<String, I18nizableText> _allMetadataLabels = new HashMap<>();
073
074    private String _pluginName;
075    
076    enum ContentTypeAttributeDataType
077    {
078        /** Metadata type */
079        METADATA,
080        
081        /** Metadata set type */
082        METADATA_SET,
083        
084        /** Fieldset type */
085        FIELDSET,
086        
087        /** Metadata reference type */
088        METADATA_REF,
089        
090        /** Indexing field type */
091        INDEXING_FIELD
092    }
093    
094    @Override
095    public void service(ServiceManager serviceManager) throws ServiceException
096    {
097        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
098        _rightsExtensionPoint = (RightsExtensionPoint) serviceManager.lookup(RightsExtensionPoint.ROLE);
099        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
100    }
101    
102    public void setPluginInfo(String pluginName, String featureName, String id)
103    {
104        _pluginName = pluginName;
105    }
106    
107    /**
108     *  retrieve content type informations
109     * @param contentTypeId - Content type's identifier
110     * @param hideInheritedMetadata - True to hide inherited metadata of a content type according to contentTypeId parameter
111     * @return a <code>Map</code> containing all informations about the content type
112     */
113    public Map<String, Object> getContentTypeInfos(String contentTypeId, boolean hideInheritedMetadata)
114    {
115        ContentType cType = _contentTypeExtensionPoint.getExtension(contentTypeId);
116        if (cType == null)
117        {
118            throw new IllegalStateException("Unknown content type '" + contentTypeId + "'");
119        }
120        
121        Map<String, Object> contentTypeInfos = new HashMap<>();
122        contentTypeInfos.put("id", cType.getId());
123        contentTypeInfos.put("label", cType.getLabel());
124        contentTypeInfos.put("description", cType.getDescription());
125        contentTypeInfos.put("iconGlyph", cType.getIconGlyph());
126        contentTypeInfos.put("iconDecorator", cType.getIconDecorator());
127        contentTypeInfos.put("largeIcon", cType.getLargeIcon());
128        contentTypeInfos.put("mediumIcon", cType.getMediumIcon());
129        contentTypeInfos.put("smallIcon", cType.getSmallIcon());
130        contentTypeInfos.put("category", cType.getCategory());
131        contentTypeInfos.put("pluginName", cType.getPluginName());
132        contentTypeInfos.put("defaultTitle", cType.getDefaultTitle());
133        contentTypeInfos.put("right", _getRightLabel(cType.getRight()));
134        contentTypeInfos.put("private", cType.isPrivate());
135        contentTypeInfos.put("abstract", cType.isAbstract());
136        contentTypeInfos.put("multilingual", cType.isMultilingual());
137        contentTypeInfos.put("mixin", cType.isMixin());
138        contentTypeInfos.put("referencetable", cType.isReferenceTable());
139        contentTypeInfos.put("simple", cType.isSimple());
140        contentTypeInfos.put("superTypes", _getSuperTypesInfos(cType));
141        contentTypeInfos.put("metadata", _getMetadata(cType, hideInheritedMetadata));
142        contentTypeInfos.put("metadataSets", _getMetadataSets(cType, hideInheritedMetadata));
143        contentTypeInfos.put("indexingModel", _getIndexingModel(cType));
144        return contentTypeInfos;
145    }
146    
147    private I18nizableText _getRightLabel(String rightId)
148    {
149        if (rightId != null)
150        {
151            Right right = _rightsExtensionPoint.getExtension(rightId);
152            if (right != null)
153            {
154                return right.getLabel();
155            }
156        }
157        return  null;
158    }
159
160    private List<Map<String, Object>> _getSuperTypesInfos(ContentType cType)
161    {
162        List<Map<String, Object>> superTypesInfos = new ArrayList<>();
163        for (String superTypeId : cType.getSupertypeIds())
164        {
165            Map<String, Object> superTypeInfos = new HashMap<>();
166            ContentType superType = _contentTypeExtensionPoint.getExtension(superTypeId);
167            superTypeInfos.put("id", superTypeId);
168            superTypeInfos.put("label", superType.getLabel());
169            superTypeInfos.put("isMixin", superType.isMixin());
170            superTypesInfos.add(superTypeInfos);
171            
172        }
173        return superTypesInfos;
174    }
175    
176    private List<Map<String, Object>> _getMetadata(ContentType cType, boolean hideInheritedMetadata)
177    {
178        List<Map<String, Object>> metadata = new ArrayList<>();
179        for (String name : cType.getMetadataNames())
180        {
181            MetadataDefinition definition = cType.getMetadataDefinition(name);
182            String referenceContentTypeId = definition.getReferenceContentType();
183            if (hideInheritedMetadata)
184            {
185                if (referenceContentTypeId.equals(cType.getId()))
186                {
187                    metadata.add(_getMetadataValues(cType, definition, null));
188                }
189            }
190            else
191            {
192                metadata.add(_getMetadataValues(cType, definition, null));
193            }
194        }
195        return metadata;
196    }
197    
198    private Map<String, Object> _getMetadataValues(ContentType cType, MetadataDefinition definition, String parentPath)
199    {
200        this._allMetadataLabels.put(definition.getName(), definition.getLabel());
201        // TODO manage transformer and richTextOutgoingReferenceExtractor
202        Map<String, Object> values = new HashMap<>();
203        values.put("dataType", ContentTypeAttributeDataType.METADATA.name().toLowerCase());
204        values.put("id", definition.getId());
205        values.put("pluginName", definition.getPluginName());
206        values.put("label", definition.getLabel());
207        values.put("description", definition.getDescription());
208        values.put("type", definition.getType().name().toLowerCase());
209        values.put("widget", definition.getWidget());
210        values.put("widgetParams", definition.getWidgetParameters());
211        values.put("defaultValue", definition.getDefaultValue());
212        values.put("name", definition.getName());
213        values.put("path", parentPath == null ? definition.getName() : parentPath + "/" + definition.getName());
214        values.put("multiple", definition.isMultiple());
215        values.put("mandatory", _isMandatoryMetadata(definition));
216        values.put("enumerated", definition.getEnumerator() != null);
217        //values.put("iconGlyph", _getMetadataIconGlyph(definition, definition.getEnumerator() != null));
218        //values.put("iconCls", _getMetadataIconGlyph(definition, definition.getEnumerator() != null));
219        String linkedContentType = definition.getContentType();
220        if (linkedContentType != null)
221        {
222            Map<String, Object> linkedCTypeInfos = new HashMap<>();
223            linkedCTypeInfos.put("id", definition.getContentType());
224            ContentType linkedCType = _contentTypeExtensionPoint.getExtension(definition.getContentType());
225            linkedCTypeInfos.put("label", linkedCType.getLabel());
226            linkedCTypeInfos.put("iconGlyph", linkedCType.getIconGlyph());
227            
228            values.put("linkedContentType", linkedCTypeInfos);
229        }
230        values.put("invertRelationPath", definition.getInvertRelationPath());
231        values.put("forceInvert", definition.getForceInvert());
232        values.put("contentTypeId", cType.getId());
233        ContentType referenceContentType = _contentTypeExtensionPoint.getExtension(definition.getReferenceContentType());
234        values.put("referenceContentTypeId", referenceContentType.getId());
235        values.put("referenceContentTypeLabel", referenceContentType.getLabel());
236        if (definition instanceof RepeaterDefinition)
237        {
238            values.putAll(_getRepeaterValues((RepeaterDefinition) definition));
239        }
240        else if (definition instanceof RichTextMetadataDefinition)
241        {
242            values.putAll(_getRichTextValues((RichTextMetadataDefinition) definition));
243        }
244        else if (definition.getEnumerator() != null)
245        {
246            Enumerator enumerator = definition.getEnumerator();
247            if (enumerator instanceof StaticEnumerator)
248            {
249                values.put("enumerator", _getEnumerator(definition));
250            }
251            else
252            {
253                values.put("enumeratorName", enumerator.getClass().getName());
254            }
255        }
256        Validator validator = definition.getValidator();
257        if (validator != null)
258        {
259            if (validator.getClass().equals(DefaultValidator.class))
260            {
261                values.put("validator", _getValidatorParameters(definition));
262            }
263            else
264            {
265                values.put("validatorName", validator.getClass().getName());
266            }
267        }
268        
269        List<Map<String, Object>> children = new ArrayList<>();
270        for (String childName : definition.getMetadataNames())
271        {
272            MetadataDefinition childDefinition = definition.getMetadataDefinition(childName);
273            children.add(_getMetadataValues(cType, childDefinition, parentPath == null ? definition.getName() : parentPath + "/" + definition.getName()));
274        }
275        values.put("leaf", children.isEmpty());
276        values.put("children", children);
277        
278        return values;
279    }
280    
281    private Map<String, Object> _getRepeaterValues(RepeaterDefinition definition)
282    {
283        Map<String, Object> values = new HashMap<>();
284        values.put("initializeSize", definition.getInitialSize());
285        values.put("minSize", definition.getMinSize());
286        values.put("maxSize", definition.getMaxSize());
287        values.put("addLabel", definition.getAddLabel());
288        values.put("deleteLabel", definition.getDeleteLabel());
289        values.put("headerLabel", definition.getHeaderLabel());
290        values.put("type", "repeater");
291        return values;
292    }
293    
294    private Map<String, Object> _getRichTextValues(RichTextMetadataDefinition definition)
295    {
296        Map<String, Object> values = new HashMap<>();
297        List<SemanticAnnotation> annotations = definition.getSemanticAnnotations();
298        List<Object> annotationsValues = new ArrayList<>();
299        for (SemanticAnnotation annotation : annotations)
300        {
301            Map<String, Object> annotationValues = new HashMap<>();
302            annotationValues.put("id", annotation.getId());
303            annotationValues.put("label", annotation.getLabel());
304            annotationValues.put("description", annotation.getDescription());
305            annotationsValues.add(annotationValues);
306        }
307        values.put("semanticAnnotations", annotationsValues);
308        return values;
309    }
310    
311    private List<Map<String, Object>> _getMetadataSets(ContentType cType, boolean hideInheritedMetadata)
312    {
313        List<Map<String, Object>> metadataSets = _getEditionMetadataSets(cType, hideInheritedMetadata);
314        metadataSets.addAll(_getViewMetadataSets(cType, hideInheritedMetadata));
315        return metadataSets;
316    }
317    
318    private List<Map<String, Object>> _getEditionMetadataSets(ContentType cType, boolean hideInheritedMetadata)
319    {
320        List<Map<String, Object>> metadataSets = new ArrayList<>();
321        for (String name : cType.getEditionMetadataSetNames(true))
322        {
323            MetadataSet metadataSet = cType.getMetadataSetForEdition(name);
324            metadataSets.add(_getMetadataSetValues(cType, metadataSet, null, hideInheritedMetadata));
325        }
326        return metadataSets;
327    }
328    
329    private List<Map<String, Object>> _getViewMetadataSets(ContentType cType, boolean hideInheritedMetadata)
330    {
331        List<Map<String, Object>> metadataSets = new ArrayList<>();
332        for (String name : cType.getViewMetadataSetNames(true))
333        {
334            MetadataSet metadataSet = cType.getMetadataSetForView(name);
335            metadataSets.add(_getMetadataSetValues(cType, metadataSet, null, hideInheritedMetadata));
336        }
337        return metadataSets;
338    }
339    
340    private Map<String, Object> _getMetadataSetValues(ContentType cType, MetadataSet metadataSet, String parentPath, boolean hideInheritedMetadata)
341    {
342        Map<String, Object> values = new HashMap<>();
343        values.put("dataType", ContentTypeAttributeDataType.METADATA_SET.name().toLowerCase());
344        values.put("name", metadataSet.getName());
345        values.put("label", metadataSet.getLabel());
346        values.put("description", metadataSet.getDescription());
347        values.put("isEdition", metadataSet.isEdition());
348        values.put("iconGlyph", metadataSet.getIconGlyph());
349        values.put("iconDecorator", metadataSet.getIconDecorator());
350        values.put("smallIcon", metadataSet.getSmallIcon());
351        values.put("mediumIcon", metadataSet.getMediumIcon());
352        values.put("largeIcon", metadataSet.getLargeIcon());
353        if (!metadataSet.getIconGlyph().isEmpty())
354        {
355            values.put("iconCls", metadataSet.getIconGlyph());
356        }
357        else
358        {
359            values.put("icon", metadataSet.getSmallIcon());
360        }
361        _processMetadataSetElementChildren(cType, metadataSet, values, parentPath, hideInheritedMetadata);
362        return values;
363    }
364    
365    private Map<String, Object> _getFieldsetValues(ContentType cType, Fieldset fieldset, String parentPath, boolean hideInheritedMetadata)
366    {
367        Map<String, Object> values = new HashMap<>();
368        values.put("dataType", ContentTypeAttributeDataType.FIELDSET.name().toLowerCase());
369        values.put("label", fieldset.getLabel());
370        values.put("role", fieldset.getRole());
371        _processMetadataSetElementChildren(cType, fieldset, values, parentPath, hideInheritedMetadata);
372        return values;
373    }
374    
375    private Map<String, Object> _getMetadataDefinitionReferenceValues(ContentType cType, MetadataDefinitionReference metadataRef, String parentPath, boolean hideInheritedMetadata)
376    {
377        Map<String, Object> values = new HashMap<>();
378        values.put("dataType", ContentTypeAttributeDataType.METADATA_REF.name().toLowerCase());
379        String metadataName = metadataRef.getMetadataName();
380        String metadataPath = parentPath != null ? parentPath + "/" + metadataName : metadataName;
381        I18nizableText metadataLabel = this._allMetadataLabels.get(metadataName);
382        if (metadataLabel != null)
383        {
384            values.put("label", metadataLabel);
385        }
386        values.put("name", metadataName);
387        values.put("path", metadataPath);
388        _processMetadataSetElementChildren(cType, metadataRef, values, parentPath != null ? parentPath + "/" + metadataName : metadataName, hideInheritedMetadata);
389        return values;
390    }
391    
392    private void _processMetadataSetElementChildren(ContentType cType, AbstractMetadataSetElement metadataSetElement, Map<String, Object> values, String parentPath, boolean hideInheritedMetadata)
393    {
394        List<Map<String, Object>> children = new ArrayList<>();
395        for (AbstractMetadataSetElement element : metadataSetElement.getElements())
396        {
397            if (element instanceof MetadataSet)
398            {
399                children.add(_getMetadataSetValues(cType, (MetadataSet) element, parentPath, hideInheritedMetadata));
400            }
401            else if (element instanceof Fieldset)
402            {
403                children.add(_getFieldsetValues(cType, (Fieldset) element, parentPath, hideInheritedMetadata));
404            }
405            else
406            {
407                MetadataDefinitionReference metadataSet = (MetadataDefinitionReference) element;
408                MetadataDefinition metadata = cType.getMetadataDefinitionByPath(metadataSet.getMetadataName());
409                if ((metadata != null && hideInheritedMetadata && cType.getId().equals(metadata.getReferenceContentType())) || !hideInheritedMetadata || metadata == null)
410                {
411                    children.add(_getMetadataDefinitionReferenceValues(cType, (MetadataDefinitionReference) element, parentPath, hideInheritedMetadata));
412                }
413            }
414        }
415        values.put("leaf", children.isEmpty());
416        values.put("children", children);
417    }
418    
419    private List<Map<String, Object>> _getIndexingModel(ContentType cType)
420    {
421        List<Map<String, Object>> result = new ArrayList<>();
422        IndexingModel indexingModel = cType.getIndexingModel();
423        Collection<IndexingField> fields = indexingModel.getFields();
424        for (IndexingField indexingField : fields)
425        {
426            result.add(_getIndexingFieldDetails(indexingField));
427        }
428        return result;
429    }
430    
431    private List<String> _getIndexingFieldPath(IndexingField indexingField)
432    {
433        List<String> indexingFieldPath = new ArrayList<>();
434        if (indexingField instanceof CustomIndexingField)
435        {
436            indexingFieldPath = null;
437        }
438        else if (indexingField instanceof MetadataIndexingField)
439        {
440            MetadataIndexingField metadataIndexingField = (MetadataIndexingField) indexingField;
441            String metadataPath = metadataIndexingField.getMetadataPath();
442            indexingFieldPath.add(metadataPath);
443        }
444        return indexingFieldPath;
445    }
446    
447    private I18nizableText _getIndexingFieldType(IndexingField indexingField)
448    {
449        I18nizableText type = new I18nizableText("plugin." + _pluginName, "PLUGINS_CONTENTTYPESEDITOR_EDITOR_TOOL_INDEXING_MODEL_METADATA_TYPE");
450        if (indexingField instanceof CustomIndexingField)
451        {
452            type = new I18nizableText("plugin." + _pluginName, "PLUGINS_CONTENTTYPESEDITOR_EDITOR_TOOL_INDEXING_MODEL_CUSTOM_METADATA_TYPE");
453        }
454        return type;
455    }
456    
457    private Map<String, Object> _getIndexingFieldDetails(IndexingField indexingField)
458    {
459        Map<String, Object> values = new HashMap<>();
460        values.put("label", indexingField.getLabel());
461        values.put("name", indexingField.getName());
462        values.put("description", indexingField.getDescription());
463        values.put("dataType", ContentTypeAttributeDataType.INDEXING_FIELD.name().toLowerCase());
464        
465        List<String> paths = _getIndexingFieldPath(indexingField);
466        values.put("path", paths);
467        if (indexingField instanceof CustomIndexingField)
468        {
469            values.put("class", indexingField.getClass());
470        }
471        I18nizableText type = _getIndexingFieldType(indexingField);
472        values.put("type", type);
473        values.put("leaf", true);
474        return values;
475    }
476   
477    private List<String> _getEnumerator(MetadataDefinition definition)
478    {
479        List<String> enumerators = new ArrayList<>();
480        Enumerator enumerator = definition.getEnumerator();
481        try
482        {
483            Map<Object, I18nizableText> entries = enumerator.getEntries();
484            for (Entry<Object, I18nizableText> entry : entries.entrySet())
485            {
486                I18nizableText enumeratorLabel = entry.getValue();
487                String translatedEnumeratorLabel = _i18nUtils.translate(enumeratorLabel);
488                String label = translatedEnumeratorLabel == null ? enumeratorLabel.toString() : translatedEnumeratorLabel;
489                enumerators.add(label);
490            }
491        }
492        catch (Exception e)
493        {
494            getLogger().error("Unable to set values for enumerator " + enumerator.getClass().getName(), e);
495        }
496        return enumerators;
497    }
498    
499    private Map<Object, Object> _getValidatorParameters(MetadataDefinition definition)
500    {
501        Map<Object, Object> validatorParameters = new HashMap<>();
502        Validator validator = definition.getValidator();
503        Map<String, Object> configuration = validator.getConfiguration();
504        Object mandatory = configuration.get("mandatory");
505        if (mandatory.toString().equals("true"))
506        {
507            Map<Object, Object> mandatoryInfo = new HashMap<>();
508            mandatoryInfo.put("key", new I18nizableText("plugin." + _pluginName, "PLUGINS_CONTENTTYPESEDITOR_EDITOR_TOOL_METADATA_VALIDATOR_MANDATORY_LABEL"));
509            mandatoryInfo.put("value", new I18nizableText(""));
510            validatorParameters.put("mandatory", mandatoryInfo);
511        }
512        Object regexp = configuration.get("regexp");
513        if (regexp != null)
514        {
515            Map<Object, Object> regexpInfo = new HashMap<>();
516            regexpInfo.put("key", new I18nizableText("plugin." + _pluginName, "PLUGINS_CONTENTTYPESEDITOR_EDITOR_TOOL_METADATA_VALIDATOR_REGEXP_LABEL"));
517            regexpInfo.put("value", regexp.toString());
518            validatorParameters.put("regexp", regexpInfo);
519        }
520        Object invalidText = configuration.get("invalidText");
521        if (invalidText != null)
522        {
523            Map<Object, Object> invalidTextInfo = new HashMap<>();
524            invalidTextInfo.put("key", new I18nizableText("plugin." + _pluginName, "PLUGINS_CONTENTTYPESEDITOR_EDITOR_TOOL_METADATA_VALIDATOR_INVALIDTEXT_LABEL"));
525            invalidTextInfo.put("value", invalidText);
526            validatorParameters.put("invalidText", invalidTextInfo);
527        }
528        return validatorParameters;
529    }
530    
531    private boolean _isMandatoryMetadata(MetadataDefinition metadataDefinition)
532    {
533        boolean isMandatory = false;
534        Validator validator = metadataDefinition.getValidator();
535        if (validator != null)
536        {
537            Map<String, Object> configuration = validator.getConfiguration();
538            if (configuration != null)
539            {
540                Object object = configuration.get("mandatory");
541                if (object instanceof Boolean)
542                {
543                    isMandatory = (boolean) object;
544                }
545            }
546        }
547        return isMandatory;
548    }
549    
550    void setContentTypeExtensionPoint(ContentTypeExtensionPoint contentTypeExtensionPoint)
551    {
552        this._contentTypeExtensionPoint = contentTypeExtensionPoint;
553    }
554
555    void setRightsExtensionPoint(RightsExtensionPoint rightsExtensionPoint)
556    {
557        this._rightsExtensionPoint = rightsExtensionPoint;
558    }
559
560}