001/*
002 *  Copyright 2018 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 */
016
017package org.ametys.plugins.contenttypeseditor.edition;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.OutputStream;
022import java.net.MalformedURLException;
023import java.nio.charset.Charset;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Properties;
032import java.util.Set;
033import java.util.stream.Collectors;
034
035import javax.xml.parsers.SAXParserFactory;
036import javax.xml.transform.OutputKeys;
037import javax.xml.transform.TransformerFactory;
038import javax.xml.transform.sax.SAXTransformerFactory;
039import javax.xml.transform.sax.TransformerHandler;
040import javax.xml.transform.stream.StreamResult;
041
042import org.apache.avalon.framework.component.Component;
043import org.apache.avalon.framework.configuration.Configuration;
044import org.apache.avalon.framework.configuration.ConfigurationException;
045import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
046import org.apache.avalon.framework.service.ServiceException;
047import org.apache.avalon.framework.service.ServiceManager;
048import org.apache.avalon.framework.service.Serviceable;
049import org.apache.cocoon.xml.AttributesImpl;
050import org.apache.cocoon.xml.XMLUtils;
051import org.apache.commons.io.IOUtils;
052import org.apache.commons.lang.StringUtils;
053import org.apache.excalibur.source.ModifiableSource;
054import org.apache.excalibur.source.Source;
055import org.apache.excalibur.source.SourceNotFoundException;
056import org.apache.excalibur.source.SourceResolver;
057import org.apache.excalibur.source.SourceUtil;
058import org.apache.xml.serializer.OutputPropertiesFactory;
059import org.xml.sax.SAXException;
060
061import org.ametys.cms.contenttype.AbstractMetadataSetElement;
062import org.ametys.cms.contenttype.ContentType;
063import org.ametys.cms.contenttype.ContentTypeDefinition;
064import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
065import org.ametys.cms.contenttype.EditContentTypeException;
066import org.ametys.cms.contenttype.EditContentTypeHelper;
067import org.ametys.cms.contenttype.Fieldset;
068import org.ametys.cms.contenttype.MetadataDefinition;
069import org.ametys.cms.contenttype.MetadataDefinitionReference;
070import org.ametys.cms.contenttype.MetadataSet;
071import org.ametys.cms.contenttype.MetadataType;
072import org.ametys.cms.contenttype.RepeaterDefinition;
073import org.ametys.runtime.i18n.I18nizableText;
074import org.ametys.runtime.parameter.DefaultValidator;
075import org.ametys.runtime.parameter.StaticEnumerator;
076import org.ametys.runtime.parameter.Validator;
077import org.ametys.runtime.plugin.component.AbstractLogEnabled;
078
079/**
080 * This component save a content type
081 */
082public class SaveContentTypeComponent extends AbstractLogEnabled implements Component, Serviceable
083{
084    /** The Avalon role name */
085    public static final String ROLE = SaveContentTypeComponent.class.getName();
086
087    /** The directory path of application i18n key */
088    private static final String __I18N_CATALOG_DIR = "context://WEB-INF/i18n/";
089
090    /** The path of rights on content types */
091    private static final String __RIGHTS_FILE = "context://WEB-INF/param/rights.xml";
092
093    /** The right category of a content type */
094    private static final String __RIGHT_CATEGORY = "plugin.cms:PLUGINS_CMS_RIGHTS_CONTENT_CATEGORY";
095
096    /** The url to save the right of content type */
097    private static final String __SAVE_CONTENT_TYPE_RIGHT = "cocoon://_plugins/contenttypes-editor/save/content-type-right";
098
099    /** The name of the default i18n catalogue */
100    private static final String __DEFAULT_CATALOGUE = "application";
101
102    /** The source resolver */
103    protected SourceResolver _sourceResolver;
104
105    /** The content type extension point instance */
106    protected ContentTypeExtensionPoint _contentTypeExtensionPoint;
107
108    /** The content type state instance */
109    protected ContentTypeStateComponent _contentTypeStateComponent;
110
111    /** The edit content type helper instance */
112    protected EditContentTypeInformationHelper _editContentTypeInformationHelper;
113
114    /** The edit content type component instance */
115    protected EditContentTypeHelper _editContentTypeHelper;
116    
117    /** Representation of i18n catalog according to the language */
118    private Map<String, I18nCatalog> _i18nCatalogs = new HashMap<>();
119
120    private List<TranslatedValue> _translatedValues;
121
122    private int _fieldsetNumber;
123
124    public void service(ServiceManager manager) throws ServiceException
125    {
126        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
127        _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
128        _contentTypeStateComponent = (ContentTypeStateComponent) manager.lookup(ContentTypeStateComponent.ROLE);
129        _editContentTypeInformationHelper = (EditContentTypeInformationHelper) manager.lookup(EditContentTypeInformationHelper.ROLE);
130        _editContentTypeHelper = (EditContentTypeHelper) manager.lookup(org.ametys.cms.contenttype.EditContentTypeHelper.ROLE);
131    }
132
133    /**
134     * Save content type
135     * 
136     * @param contentTypeInfos all information about the content type to save
137     * @return True if the content type was successfully saved
138     */
139    public boolean saveContentType(Map<String, Object> contentTypeInfos)
140    {
141        boolean success = true;
142        this._translatedValues = new ArrayList<>();
143
144        String realContentTypeId = contentTypeInfos.get("id").toString();
145        ContentTypeDefinition contentTypeDefinition = _getContentTypeDefinition(contentTypeInfos);
146
147        // Sax content type file
148        if (_contentTypeStateComponent.getContentTypeMarkedAsNew().contains(realContentTypeId))
149        {
150            try
151            {
152                _editContentTypeHelper.createContentType(contentTypeDefinition);
153
154                // Sax right param
155                if (success)
156                {
157                    try
158                    {
159                        // Adding right in param/rights.xml
160                        if (contentTypeDefinition.getRight() != null)
161                        {
162                            String pluginName = contentTypeInfos.get("pluginName").toString();
163                            String idKey = _generateContentTypeIdKey(realContentTypeId);
164                            String rightLabelKey = _generateI18nKey(pluginName, "right_create", idKey, "label");
165                            String rightDescriptionKey = _generateI18nKey(pluginName, "right_create", idKey, "description");
166                            _saxRightsParam(contentTypeDefinition.getRight(), rightLabelKey, rightDescriptionKey);
167                        }
168                    }
169                    catch (Exception e)
170                    {
171                        getLogger().error("Error when trying to save the right of new content type '" + realContentTypeId + "'", e);
172                        success = false;
173                    }
174                }
175            }
176            catch (EditContentTypeException e)
177            {
178                success = false;
179            }
180        }
181        else
182        {
183            try
184            {
185                _editContentTypeHelper.editContentType(contentTypeDefinition);
186            }
187            catch (EditContentTypeException e)
188            {
189                success = false;
190            }
191        }
192
193        // Sax i18n catalog
194        if (success)
195        {
196            try
197            {
198                // Adding i18n key to catalog
199                Map<String, Map<String, String>> i18nMessageTranslations = _createNewI18nCatalogs();
200                _saxCatalogs(i18nMessageTranslations);
201            }
202            catch (Exception e)
203            {
204                getLogger().error("Error when trying to save i18n key of new content type '" + realContentTypeId + "'", e);
205                success = false;
206            }
207        }
208
209        return success;
210    }
211
212    private ContentTypeDefinition _getContentTypeDefinition(Map<String, Object> contentTypeInfos)
213    {
214        String realContentTypeId = contentTypeInfos.get("id").toString();
215        String contentTypeId = realContentTypeId.replaceFirst("content-type.", "");
216        String pluginName = contentTypeInfos.get("pluginName").toString();
217
218        ContentType existingContentType = _contentTypeExtensionPoint.getExtension(realContentTypeId);
219
220        ContentTypeDefinition contentType = new ContentTypeDefinition(contentTypeInfos.get("id").toString());
221        contentType.setPluginName(pluginName);
222        _addGeneralInformation(contentType, existingContentType, contentTypeInfos, pluginName, contentTypeId);
223        _addMetadata(contentType, existingContentType, contentTypeInfos, pluginName, realContentTypeId);
224        _addMetadataSet(contentType, contentTypeInfos, pluginName, contentTypeId);
225
226        return contentType;
227    }
228
229    private void _addGeneralInformation(ContentTypeDefinition contentType, ContentType existingContentType, Map<String, Object> contentTypeInfos, String pluginName, String contentTypeId)
230    {
231        I18nizableText label = _getI18nizableText(existingContentType != null ? existingContentType.getLabel() : null, contentTypeInfos.get("label"), pluginName, "contenttype", contentTypeId, "label");
232        contentType.setLabel(label);
233
234        I18nizableText defaultTitle = _getI18nizableText(existingContentType != null ? existingContentType.getDefaultTitle() : null, contentTypeInfos.get("defaultTitle"), pluginName, "contenttype", contentTypeId, "default_title");
235        contentType.setDefaultTitle(defaultTitle);
236
237        I18nizableText description = _getI18nizableText(existingContentType != null ? existingContentType.getDescription() : null, contentTypeInfos.get("description"), pluginName, "contenttype", contentTypeId, "description");
238        contentType.setDescription(description);
239
240        _addCategory(contentType, contentTypeInfos.get("category"), contentTypeInfos.get("newCategory"), pluginName);
241
242        _addIconGlyph(contentType, contentTypeInfos.get("iconGlyph"));
243
244        _addTags(contentType, contentTypeInfos.get("tags"), contentTypeInfos.get("private"), contentTypeInfos.get("referencetable"), contentTypeInfos.get("mixin"));
245
246        _addSupertypeIds(contentType, contentTypeInfos.get("superTypes"));
247
248        _addIsAbstract(contentType, contentTypeInfos.get("abstract"));
249
250        _addRight(contentType, contentTypeId, pluginName);
251    }
252
253    private void _addCategory(ContentTypeDefinition contentTypeDefinition, Object recoveredCategory, Object recoveredNewCategory, String pluginName)
254    {
255        if (recoveredCategory != null)
256        {
257            if (recoveredCategory instanceof Map)
258            {
259                Map categoryMap = (Map) recoveredCategory;
260                if (!categoryMap.isEmpty())
261                {
262                    Object isNewObject = categoryMap.get("isNew");
263                    if (isNewObject != null && isNewObject instanceof Boolean)
264                    {
265                        I18nizableText category = null;
266                        boolean isNew = (Boolean) isNewObject;
267                        if (isNew)
268                        {
269                            category = _getNewCategory(recoveredNewCategory, pluginName);
270                        }
271                        else
272                        {
273                            category = _getExistingCategory(categoryMap);
274                        }
275                        contentTypeDefinition.setCategory(category);
276                    }
277                }
278            }
279        }
280    }
281
282    private I18nizableText _getNewCategory(Object recoveredNewCategory, String pluginName)
283    {
284        I18nizableText category = null;
285
286        if (recoveredNewCategory != null && recoveredNewCategory instanceof Map)
287        {
288            Map newCategory = (Map) recoveredNewCategory;
289
290            if (!newCategory.isEmpty())
291            {
292                Object i18n = newCategory.get("isMultilingual");
293                if (i18n instanceof Boolean)
294                {
295                    boolean isI18n = (boolean) i18n;
296                    if (isI18n)
297                    {
298                        Object recoveredI18nValues = newCategory.get("values");
299                        if (recoveredI18nValues != null && recoveredI18nValues instanceof Map)
300                        {
301                            Map i18nValues = (Map) recoveredI18nValues;
302
303                            String categoryKey = _generateCategoryI18nKey(pluginName, newCategory);
304                            category = new I18nizableText("plugin.contenttypes-editor", categoryKey);
305
306                            // Add key and values to the TranslatedValue object
307                            TranslatedValue translatedValue = new TranslatedValue(categoryKey, i18nValues);
308                            _translatedValues.add(translatedValue);
309                        }
310                    }
311                    else
312                    {
313                        Object value = newCategory.get("values");
314                        if (value instanceof String)
315                        {
316                            category = new I18nizableText((String) value);
317                        }
318                    }
319                    _editContentTypeInformationHelper.addNewCategory(newCategory);
320                }
321            }
322        }
323
324        return category;
325    }
326
327    private I18nizableText _getExistingCategory(Map categoryMap)
328    {
329        I18nizableText category = null;
330        Object i18n = categoryMap.get("isMultilingual");
331        if (i18n instanceof Boolean)
332        {
333            boolean isI18n = (boolean) i18n;
334            if (isI18n)
335            {
336                Object recoveredKey = categoryMap.get("key");
337                Object recoveredCatalogue = categoryMap.get("catalogue");
338                if (recoveredKey instanceof String && recoveredCatalogue instanceof String)
339                {
340                    category = new I18nizableText((String) recoveredCatalogue, (String) recoveredKey);
341                }
342            }
343            else
344            {
345                Object value = categoryMap.get("values");
346                if (value instanceof String)
347                {
348                    category = new I18nizableText((String) value);
349                }
350            }
351        }
352        return category;
353    }
354
355    private void _addIconGlyph(ContentTypeDefinition contentTypeDefinition, Object recoverIconGlyph)
356    {
357        if (recoverIconGlyph instanceof String)
358        {
359            String icon = (String) recoverIconGlyph;
360            if (StringUtils.isNotBlank(icon))
361            {
362                contentTypeDefinition.setIconGlyph(icon);
363            }
364        }
365    }
366
367    private void _addTags(ContentTypeDefinition contentTypeDefinition, Object recoverTags, Object recoverIsPrivate, Object recoverIsReferenceTable, Object recoverIsMixin)
368    {
369        Set<String> tags = new HashSet<>();
370        if (recoverTags != null && recoverTags instanceof String)
371        {
372            String[] tagArray = ((String) recoverTags).split(",");
373            for (String tag : tagArray)
374            {
375                if (StringUtils.isNotBlank(tag))
376                {
377                    tags.add(tag);
378                }
379            }
380        }
381        if (recoverIsPrivate != null && recoverIsPrivate instanceof Boolean && (boolean) recoverIsPrivate)
382        {
383            tags.add("private");
384        }
385        if (recoverIsReferenceTable != null && recoverIsReferenceTable instanceof Boolean && (boolean) recoverIsReferenceTable)
386        {
387            tags.add("reference-table");
388        }
389        if (recoverIsMixin != null && recoverIsMixin instanceof Boolean && (boolean) recoverIsMixin)
390        {
391            tags.add("mixin");
392        }
393
394        if (!tags.isEmpty())
395        {
396            contentTypeDefinition.setTags(tags);
397        }
398    }
399
400    private void _addSupertypeIds(ContentTypeDefinition contentTypeDefinition, Object recoverSupertypes)
401    {
402        if (recoverSupertypes != null && recoverSupertypes instanceof List)
403        {
404            List<LinkedHashMap<String, String>> supertypes = (List<LinkedHashMap<String, String>>) recoverSupertypes;
405            List<String> supertypeIds = new ArrayList<>();
406            supertypeIds = supertypes.stream().map(s -> s.get("id")).collect(Collectors.toList());
407            contentTypeDefinition.setSupertypeIds(supertypeIds.toArray(new String[supertypeIds.size()]));
408        }
409    }
410
411    private void _addIsAbstract(ContentTypeDefinition contentTypeDefinition, Object recoverIsAbstract)
412    {
413        boolean isAbstract = recoverIsAbstract != null && recoverIsAbstract instanceof Boolean && (boolean) recoverIsAbstract;
414        contentTypeDefinition.setIsAbstract(isAbstract);
415    }
416
417    private void _addRight(ContentTypeDefinition contentTypeDefinition, String contentTypeId, String pluginName)
418    {
419        String right = pluginName + "_Right_" + _generateContentTypeIdKey(contentTypeId) + "_Create";
420        contentTypeDefinition.setRight(right);
421    }
422
423    private void _addMetadata(ContentTypeDefinition contentType, ContentType existingContentType, Map<String, Object> contentTypeInfos, String pluginName, String realContentTypeId)
424    {
425        Object recoverMetadataList = contentTypeInfos.get("metadata");
426        if (recoverMetadataList != null && recoverMetadataList instanceof List)
427        {
428            List metadataList = (List) recoverMetadataList;
429            List<MetadataDefinition> metadata = new ArrayList<>();
430            for (Object recoverMetadata : metadataList)
431            {
432                if (recoverMetadata instanceof Map)
433                {
434                    Map metadataInfo = (Map) recoverMetadata;
435                    Object recoveredReferenceContentTypeId = metadataInfo.get("referenceContentTypeId");
436                    if (recoveredReferenceContentTypeId != null && recoveredReferenceContentTypeId instanceof String
437                            && ((String) recoveredReferenceContentTypeId).equals(realContentTypeId))
438                    {
439                        // if it's a metadata of current content type
440                        Object recoverName = metadataInfo.get("name");
441                        MetadataDefinition metadataDefinition = null;
442                        if (existingContentType != null && recoverName instanceof String)
443                        {
444                            String name = (String) recoverName;
445                            metadataDefinition = existingContentType.getMetadataDefinition(name);
446                        }
447                        String contentTypeId = realContentTypeId.replaceFirst("content-type.", "");
448                        metadata.add(_getMetadata(metadataInfo, metadataDefinition, pluginName, contentTypeId));
449                    }
450                }
451            }
452            contentType.setMetadata(metadata);
453        }
454    }
455
456    private MetadataDefinition _getMetadata(Map<String, Object> metadataInfo, MetadataDefinition existingMetadataDefinition, String pluginName, String contentTypeId)
457    {
458        MetadataDefinition metadataDefinition = null;
459
460        Object recoverType = metadataInfo.get("type");
461        if (recoverType != null && recoverType instanceof String)
462        {
463            String name = _getString(metadataInfo.get("name"));
464
465            String type = (String) recoverType;
466            if (type.equals("repeater"))
467            {
468                metadataDefinition = _getRepeater(metadataInfo, (RepeaterDefinition) existingMetadataDefinition, name, pluginName, contentTypeId);
469                _addChildrens(metadataDefinition, existingMetadataDefinition, metadataInfo, pluginName, contentTypeId);
470            }
471            else if (type.equals("composite"))
472            {
473                metadataDefinition = new MetadataDefinition();
474                metadataDefinition.setType(MetadataType.COMPOSITE);
475                _addChildrens(metadataDefinition, existingMetadataDefinition, metadataInfo, pluginName, contentTypeId);
476            }
477            else
478            {
479                metadataDefinition = _getSimpleMetadata(metadataInfo, existingMetadataDefinition, pluginName, contentTypeId, name, type);
480            }
481
482            _addMetadataGeneralInformation(metadataDefinition, existingMetadataDefinition, metadataInfo, name, pluginName, contentTypeId);
483
484        }
485        return metadataDefinition;
486    }
487
488    private RepeaterDefinition _getRepeater(Map<String, Object> metadataInfo, RepeaterDefinition existingRepeaterDefinition, String name, String pluginName, String contentTypeId)
489    {
490        RepeaterDefinition repeater = new RepeaterDefinition();
491        repeater.setInitialSize(_getInteger(metadataInfo.get("initializeSize")));
492        repeater.setMinSize(_getInteger(metadataInfo.get("minSize")));
493        repeater.setMaxSize(_getInteger(metadataInfo.get("maxSize")));
494
495        I18nizableText addLabel = _getI18nizableText(existingRepeaterDefinition != null ? existingRepeaterDefinition.getAddLabel() : null, metadataInfo.get("addLabel"), pluginName,
496                "contenttype", contentTypeId, "metadata_" + name + "_add_label");
497        repeater.setAddLabel(addLabel);
498
499        I18nizableText deleteLabel = _getI18nizableText(existingRepeaterDefinition != null ? existingRepeaterDefinition.getDeleteLabel() : null, metadataInfo.get("deleteLabel"),
500                pluginName, "contenttype", contentTypeId, "metadata_" + name + "_delete_label");
501        repeater.setDeleteLabel(deleteLabel);
502
503        repeater.setType(MetadataType.COMPOSITE);
504
505        return repeater;
506    }
507
508    private MetadataDefinition _getSimpleMetadata(Map<String, Object> metadataInfo, MetadataDefinition existingMetadataDefinition, String pluginName, String contentTypeId,
509            String name, String type)
510    {
511        MetadataDefinition metadataDefinition = new MetadataDefinition();
512        _addSimpleMetadataType(metadataDefinition, metadataInfo, type);
513        _addWidget(metadataDefinition, metadataInfo, name, pluginName, contentTypeId);
514        _addEnumerator(metadataDefinition, metadataInfo, name, pluginName, contentTypeId);
515        _addValidator(metadataDefinition, existingMetadataDefinition, metadataInfo, name, pluginName, contentTypeId);
516        return metadataDefinition;
517    }
518
519    private void _addSimpleMetadataType(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String type)
520    {
521        MetadataType[] metadataTypes = MetadataType.values();
522        for (MetadataType metadataType : metadataTypes)
523        {
524            if (metadataType.toString().toLowerCase().equals(type.toLowerCase()))
525            {
526                metadataDefinition.setType(metadataType);
527                if (metadataType == MetadataType.CONTENT)
528                {
529                    Object recoverLinkedContentType = metadataInfo.get("linkedContentType");
530                    if (recoverLinkedContentType != null && recoverLinkedContentType instanceof Map)
531                    {
532                        Map linkedContentType = (Map) recoverLinkedContentType;
533                        Object recoverId = linkedContentType.get("id");
534                        if (recoverId != null && recoverId instanceof String)
535                        {
536                            metadataDefinition.setContentType((String) recoverId);
537                            String invert = _getString(metadataInfo.get("invertRelationPath"));
538                            metadataDefinition.setInvertRelationPath(invert);
539                        }
540                    }
541                }
542                break;
543            }
544        }
545    }
546
547    private void _addWidget(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String name, String pluginName, String contentTypeId)
548    {
549        String widget = _getString(metadataInfo.get("widget"));
550        metadataDefinition.setWidget(widget);
551
552        Map<String, I18nizableText> widgetParams = null;
553        Object recoverWidgetParams = metadataInfo.get("widgetParams");
554        if (recoverWidgetParams != null && recoverWidgetParams instanceof List)
555        {
556            widgetParams = new HashMap<>();
557            List params = (List) recoverWidgetParams;
558            for (Object recoverWidgetParam : params)
559            {
560                if (recoverWidgetParam != null && recoverWidgetParam instanceof Map)
561                {
562                    Map widgetParam = (Map) recoverWidgetParam;
563                    String recoverLabel = _getString(widgetParam.get("label"));
564                    I18nizableText values = _getI18nizableText(null, widgetParam.get("value"), pluginName, "contentttype", contentTypeId,
565                            "metadata_" + name + "_" + recoverLabel + "_widget_param");
566                    widgetParams.put(recoverLabel, values);
567                }
568            }
569        }
570        metadataDefinition.setWidgetParameters(widgetParams);
571    }
572
573    private void _addEnumerator(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String name, String pluginName, String contentTypeId)
574    {
575        String enumeratorName = _getString(metadataInfo.get("customEnumeratorClass"));
576        if (StringUtils.isNotBlank(enumeratorName))
577        {
578            _addCustomEnumerator(metadataDefinition, metadataInfo, enumeratorName);
579        }
580        else if (metadataInfo.get("defaultEnumerator") != null)
581        {
582            _addDefaultEnumerator(metadataDefinition, metadataInfo, name, pluginName, contentTypeId);
583        }
584    }
585
586    private void _addCustomEnumerator(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String enumeratorName)
587    {
588        metadataDefinition.setCustomEnumerator(enumeratorName);
589
590        String customEnumeratorConfiguration = _getString(metadataInfo.get("customEnumeratorConfiguration"));
591        if (customEnumeratorConfiguration != null)
592        {
593            DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
594            try
595            {
596                Configuration configuration = builder.build(IOUtils.toInputStream(customEnumeratorConfiguration, Charset.forName("UTF-8")));
597                metadataDefinition.setEnumeratorConfiguration(configuration);
598            }
599            catch (ConfigurationException | SAXException | IOException e)
600            {
601                getLogger().error("The XML configuration of custom enumerator is not valid", e);
602            }
603        }
604    }
605
606    private void _addDefaultEnumerator(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String name, String pluginName, String contentTypeId)
607    {
608        Object recoverEnumerator = metadataInfo.get("defaultEnumerator");
609        if (recoverEnumerator instanceof List)
610        {
611            StaticEnumerator staticEnumerator = new StaticEnumerator();
612            List enumerator = (List) recoverEnumerator;
613            for (Object recoverValue : enumerator)
614            {
615                if (recoverValue != null && recoverValue instanceof Map)
616                {
617                    Map value = (Map) recoverValue;
618                    Object recoverEnumLabel = value.get("label");
619                    if (recoverEnumLabel != null && recoverEnumLabel instanceof String)
620                    {
621                        String enumLabel = (String) recoverEnumLabel;
622                        Object recoveredEnumValue = value.get("value");
623                        I18nizableText enumValue = _getI18nizableText(null, recoveredEnumValue, pluginName, "contenttype", contentTypeId,
624                                "metadata_" + name + "_" + enumLabel + "_enumeration");
625                        staticEnumerator.add(enumValue, enumLabel);
626                    }
627                }
628            }
629            metadataDefinition.setEnumerator(staticEnumerator);
630        }
631    }
632
633    private void _addValidator(MetadataDefinition metadataDefinition, MetadataDefinition existingMetadataDefinition, Map<String, Object> metadataInfo, String name,
634            String pluginName, String contentTypeId)
635    {
636        String validatorName = _getString(metadataInfo.get("customValidatorClass"));
637        if (StringUtils.isNotBlank(validatorName))
638        {
639            _addCustomValidator(metadataDefinition, metadataInfo, validatorName);
640        }
641        else if (metadataInfo.get("defaultValidator") != null)
642        {
643            _addDefaultValidator(metadataDefinition, existingMetadataDefinition, metadataInfo, name, pluginName, contentTypeId);
644        }
645    }
646
647    private void _addCustomValidator(MetadataDefinition metadataDefinition, Map<String, Object> metadataInfo, String validatorName)
648    {
649        metadataDefinition.setCustomValidator(validatorName);
650
651        String customValidatorConfiguration = _getString(metadataInfo.get("customValidatorConfiguration"));
652        if (customValidatorConfiguration != null)
653        {
654            DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
655            try
656            {
657                Configuration configuration = builder.build(IOUtils.toInputStream(customValidatorConfiguration, Charset.forName("UTF-8")));
658                metadataDefinition.setValidatorConfiguration(configuration);
659            }
660            catch (ConfigurationException | SAXException | IOException e)
661            {
662                getLogger().error("The XML configuration of custom validator is not valid", e);
663            }
664        }
665    }
666
667    private void _addDefaultValidator(MetadataDefinition metadataDefinition, MetadataDefinition existingMetadataDefinition, Map<String, Object> metadataInfo, String name,
668            String pluginName, String contentTypeId)
669    {
670        Object recoverValidator = metadataInfo.get("defaultValidator");
671        if (recoverValidator instanceof Map)
672        {
673            Map validator = (Map) recoverValidator;
674            boolean isMandatory = _getBoolean(validator.get("mandatory"));
675            String regexp = _getString(validator.get("regexp"));
676
677            I18nizableText invalidText = null;
678            I18nizableText existingInvalidText = null;
679            Object recoverInvalidText = validator.get("invalidText");
680            if (recoverInvalidText != null && recoverInvalidText instanceof Map)
681            {
682                if (existingMetadataDefinition != null)
683                {
684                    Validator existingValidator = existingMetadataDefinition.getValidator();
685                    Map<String, Object> existingConfiguration = existingValidator.getConfiguration();
686                    if (existingConfiguration.get("invalidText") != null)
687                    {
688                        existingInvalidText = (I18nizableText) existingConfiguration.get("invalidText");
689                    }
690                }
691                invalidText = _getI18nizableText(existingInvalidText, recoverInvalidText, pluginName, "contenttype", contentTypeId, "metadata_" + name + "validator_invalid_text");
692            }
693            DefaultValidator defaultValidator = new DefaultValidator(regexp, invalidText, isMandatory);
694            metadataDefinition.setValidator(defaultValidator);
695        }
696    }
697
698    private void _addMetadataGeneralInformation(MetadataDefinition metadataDefinition, MetadataDefinition existingMetadataDefinition, Map<String, Object> metadataInfo, String name,
699            String pluginName, String contentTypeId)
700    {
701        if (metadataDefinition != null)
702        {
703            metadataDefinition.setName(name);
704
705            I18nizableText label = _getI18nizableText(existingMetadataDefinition != null ? existingMetadataDefinition.getLabel() : null, metadataInfo.get("label"), pluginName,
706                    "contenttype", contentTypeId, "metadata_" + name + "_label");
707            metadataDefinition.setLabel(label);
708
709            I18nizableText description = _getI18nizableText(existingMetadataDefinition != null ? existingMetadataDefinition.getLabel() : null, metadataInfo.get("description"),
710                    pluginName, "contenttype", contentTypeId, "metadata_" + name + "_description");
711            metadataDefinition.setDescription(description);
712
713            boolean isMultiple = _getBoolean(metadataInfo.get("multiple"));
714            metadataDefinition.setMultiple(isMultiple);
715        }
716    }
717
718    private void _addChildrens(MetadataDefinition metadataDefinition, MetadataDefinition existingMetadataDefinition, Map<String, Object> metadataInfo, String pluginName,
719            String contentTypeId)
720    {
721        Object recoverChildrens = metadataInfo.get("children");
722        if (recoverChildrens != null && recoverChildrens instanceof List)
723        {
724            List childrens = (List) recoverChildrens;
725            if (!childrens.isEmpty())
726            {
727                for (Object recoverChildren : childrens)
728                {
729                    if (recoverChildren != null && recoverChildren instanceof Map)
730                    {
731                        Map children = (Map) recoverChildren;
732                        Object recoverName = children.get("name");
733                        MetadataDefinition existingChildrenMetadataDefinition = null;
734                        if (existingMetadataDefinition != null && recoverName != null && recoverName instanceof String)
735                        {
736                            existingChildrenMetadataDefinition = existingMetadataDefinition.getMetadataDefinition((String) recoverName);
737                        }
738                        MetadataDefinition childrenMetadataDefinition = _getMetadata(children, existingChildrenMetadataDefinition, pluginName, contentTypeId);
739                        metadataDefinition.addMetadata(childrenMetadataDefinition);
740                    }
741                }
742            }
743        }
744    }
745
746    private void _addMetadataSet(ContentTypeDefinition contentTypeDefinition, Map<String, Object> contentTypeInfos, String pluginName, String contentTypeId)
747    {
748        Object recoverMetadataSets = contentTypeInfos.get("metadataSets");
749        if (recoverMetadataSets != null && recoverMetadataSets instanceof List)
750        {
751            List<AbstractMetadataSetElement> metadataSetList = new ArrayList<>();
752            List metadataSets = (List) recoverMetadataSets;
753
754            _fieldsetNumber = 1;
755            for (Object recoverMetadataSet : metadataSets)
756            {
757                if (recoverMetadataSet != null && recoverMetadataSet instanceof Map)
758                {
759                    Map metadataSetInfo = (Map) recoverMetadataSet;
760                    Object recoverMetadataSetName = metadataSetInfo.get("name");
761                    if (recoverMetadataSetName != null && recoverMetadataSetName instanceof String)
762                    {
763                        String metadataSetName = (String) recoverMetadataSetName;
764                        Object recoverIsMetadataSetEdition = metadataSetInfo.get("isEdition");
765                        if (recoverIsMetadataSetEdition != null && recoverIsMetadataSetEdition instanceof Boolean)
766                        {
767                            boolean isMetadataSetEdition = (boolean) recoverIsMetadataSetEdition;
768                            metadataSetList.add(_getMetadataSet(contentTypeDefinition, metadataSetInfo, metadataSetName, isMetadataSetEdition, pluginName, contentTypeId));
769                        }
770                    }
771                }
772            }
773            contentTypeDefinition.setMetadataSet(metadataSetList);
774        }
775    }
776
777    private AbstractMetadataSetElement _getMetadataSet(ContentTypeDefinition contentTypeDefinition, Map<String, Object> metadataSetInfo, String metadataSetName,
778            boolean isMetadataSetEdition, String pluginName, String contentTypeId)
779    {
780        AbstractMetadataSetElement metadataSet = null;
781
782        String dataType = _getString(metadataSetInfo.get("dataType"));
783        if (StringUtils.isNotBlank(dataType))
784        {
785            if (dataType.equals("metadata_set"))
786            {
787                metadataSet = _getMetadataSetDataType(contentTypeDefinition, metadataSetInfo, metadataSetName, isMetadataSetEdition, pluginName, contentTypeId);
788            }
789            else if (dataType.equals("fieldset"))
790            {
791                metadataSet = _getFieldSetDataType(contentTypeDefinition, metadataSetInfo, metadataSetName, isMetadataSetEdition, pluginName, contentTypeId);
792            }
793            else if (dataType.equals("metadata_ref"))
794            {
795                if (_isMetadataOfCurrentContentTypeDefinition(contentTypeDefinition, metadataSetName))
796                {
797                    metadataSet = new MetadataDefinitionReference(metadataSetName);
798                    _addChildrensMetadataSet(metadataSet, contentTypeDefinition, metadataSetInfo, pluginName, contentTypeId);
799                }
800            }
801
802        }
803        return metadataSet;
804    }
805
806    private MetadataSet _getMetadataSetDataType(ContentTypeDefinition contentTypeDefinition, Map<String, Object> metadataSetInfo, String metadataSetName, boolean isMetadataSetEdition, String pluginName, String contentTypeId)
807    {
808        MetadataSet metadataSet = new MetadataSet();
809        metadataSet.setEdition(isMetadataSetEdition);
810        metadataSet.setName(metadataSetName);
811
812        Object recoverLabel = metadataSetInfo.get("label");
813        I18nizableText label = _getI18nizableText(null, recoverLabel, pluginName, "contenttype", contentTypeId,
814                "metadatasets_" + metadataSetName + "_" + (isMetadataSetEdition ? "edition" : "view") + "_label");
815        metadataSet.setLabel(label);
816
817        Object recoverDescription = metadataSetInfo.get("description");
818        I18nizableText description = _getI18nizableText(null, recoverDescription, pluginName, "contenttype", contentTypeId,
819                "metadatasets_" + metadataSetName + "_" + (isMetadataSetEdition ? "edition" : "view") + "_description");
820        metadataSet.setDescription(description);
821
822        _addChildrensMetadataSet(metadataSet, contentTypeDefinition, metadataSetInfo, pluginName, contentTypeId);
823
824        return metadataSet;
825    }
826
827    private Fieldset _getFieldSetDataType(ContentTypeDefinition contentTypeDefinition, Map<String, Object> metadataSetInfo, String metadataSetName, boolean isMetadataSetEdition, String pluginName, String contentTypeId)
828    {
829        Fieldset fieldSet = new Fieldset();
830
831        Object recoverLabel = metadataSetInfo.get("label");
832        I18nizableText label = _getI18nizableText(null, recoverLabel, pluginName, "contenttype", contentTypeId,
833                "metadatasets_" + metadataSetName + "_" + (isMetadataSetEdition ? "edition" : "view") + "_fieldset_" + _fieldsetNumber + "_label");
834        fieldSet.setLabel(label);
835
836        _addChildrensMetadataSet(fieldSet, contentTypeDefinition, metadataSetInfo, pluginName, contentTypeId);
837
838        return fieldSet;
839    }
840
841    private boolean _isMetadataOfCurrentContentTypeDefinition(ContentTypeDefinition contentTypeDefinition, String metadataName)
842    {
843        boolean isMetadataOfCurrentContentTypeDefinition = false;
844
845        List<MetadataDefinition> metadata = contentTypeDefinition.getMetadata();
846        for (MetadataDefinition metadataDefinition : metadata)
847        {
848            if (metadataDefinition.getName().equals(metadataName))
849            {
850                isMetadataOfCurrentContentTypeDefinition = true;
851                break;
852            }
853            else
854            {
855                isMetadataOfCurrentContentTypeDefinition = _isMetadataOfCurrentContentTypeDefinition(metadataName, metadataDefinition);
856            }
857        }
858        return isMetadataOfCurrentContentTypeDefinition;
859    }
860
861    private boolean _isMetadataOfCurrentContentTypeDefinition(String metadataName, MetadataDefinition metadataDefinition)
862    {
863        boolean isMetadataOfCurrentContentTypeDefinition = false;
864
865        if (metadataDefinition.getName().equals(metadataName))
866        {
867            isMetadataOfCurrentContentTypeDefinition = true;
868        }
869        else
870        {
871            Set<String> metadataNames = metadataDefinition.getMetadataNames();
872            for (String childrenMetadataName : metadataNames)
873            {
874                MetadataDefinition metadata = metadataDefinition.getMetadataDefinition(childrenMetadataName);
875                if (_isMetadataOfCurrentContentTypeDefinition(metadataName, metadata))
876                {
877                    isMetadataOfCurrentContentTypeDefinition = true;
878                    break;
879                }
880            }
881        }
882        return isMetadataOfCurrentContentTypeDefinition;
883    }
884
885    private void _addChildrensMetadataSet(AbstractMetadataSetElement metadataSet, ContentTypeDefinition contentTypeDefinition, Map<String, Object> metadataSetInfo, String pluginName, String contentTypeId)
886    {
887        Object recoverChildrens = metadataSetInfo.get("children");
888        if (recoverChildrens != null && recoverChildrens instanceof List)
889        {
890            List<AbstractMetadataSetElement> childrenMetadataSet = new ArrayList<>();
891
892            List childrens = (List) recoverChildrens;
893            for (Object recoverChildren : childrens)
894            {
895                if (recoverChildren != null && recoverChildren instanceof Map)
896                {
897                    Map children = (Map) recoverChildren;
898                    Object recoverChildrenMetadataSetName = children.get("name");
899                    if (recoverChildrenMetadataSetName != null && recoverChildrenMetadataSetName instanceof String)
900                    {
901                        String childrenMetadataSetName = (String) recoverChildrenMetadataSetName;
902                        Object recoverMetadataIsMetadataSetEdition = children.get("isEdition");
903                        if (recoverMetadataIsMetadataSetEdition != null && recoverMetadataIsMetadataSetEdition instanceof Boolean)
904                        {
905                            boolean isChildrenMetadataSetEdition = (boolean) recoverMetadataIsMetadataSetEdition;
906                            childrenMetadataSet.add(_getMetadataSet(contentTypeDefinition, children, childrenMetadataSetName, isChildrenMetadataSetEdition, pluginName, contentTypeId));
907                        }
908                        else
909                        {
910                            boolean isChildrenMetadataSetEdition = false;
911                            childrenMetadataSet.add(_getMetadataSet(contentTypeDefinition, children, childrenMetadataSetName, isChildrenMetadataSetEdition, pluginName, contentTypeId));
912                        }
913                    }
914                }
915            }
916            metadataSet.setElements(childrenMetadataSet);
917        }
918    }
919
920    private boolean _getBoolean(Object recoverValue)
921    {
922        return recoverValue instanceof Boolean && (boolean) recoverValue;
923    }
924
925    private String _getString(Object recoverValue)
926    {
927        String value = null;
928        if (recoverValue != null && recoverValue instanceof String)
929        {
930            value = (String) recoverValue;
931        }
932        return value;
933    }
934
935    private int _getInteger(Object recoverValue)
936    {
937        int value = 0;
938        if (recoverValue != null && recoverValue instanceof Integer)
939        {
940            value = (int) recoverValue;
941        }
942        return value;
943    }
944
945    private I18nizableText _getI18nizableText(I18nizableText i18nizableText, Object recoverValue, String pluginName, String type, String contentTypeId, String label)
946    {
947        I18nizableText text = null;
948        if (recoverValue != null && recoverValue instanceof LinkedHashMap)
949        {
950            String key = _generateI18nKey(pluginName, type, contentTypeId, label);
951            text = _getI18nizableText(i18nizableText, recoverValue, key);
952        }
953        return text;
954    }
955
956    private I18nizableText _getI18nizableText(I18nizableText existingI18nizableText, Object recoverValue, String key)
957    {
958        I18nizableText text = null;
959
960        if (recoverValue != null && recoverValue instanceof Map)
961        {
962            Map value = (Map) recoverValue;
963            Object isMultilingual = value.get("isMultilingual");
964            if (isMultilingual != null && isMultilingual instanceof Boolean)
965            {
966                boolean isI18n = (Boolean) isMultilingual;
967                if (isI18n)
968                {
969                    Object recoveredI18nValues = value.get("values");
970                    if (recoveredI18nValues != null && recoveredI18nValues instanceof Map)
971                    {
972                        Map i18nValues = (Map) recoveredI18nValues;
973                        if (!i18nValues.isEmpty())
974                        {
975                            // There is an existing key for the data
976                            if (existingI18nizableText != null && existingI18nizableText.isI18n())
977                            {
978                                text = existingI18nizableText;
979                            }
980                            else
981                            {
982                                text = new I18nizableText(__DEFAULT_CATALOGUE, key);
983                            }
984                            // Add key and values to the TranslatedValue object
985                            TranslatedValue translatedValue = new TranslatedValue(key, i18nValues);
986                            _translatedValues.add(translatedValue);
987                        }
988                    }
989                }
990                else
991                {
992                    Object recoverLabel = value.get("values");
993                    if (recoverLabel instanceof String)
994                    {
995                        String label = (String) recoverLabel;
996                        if (StringUtils.isNotBlank(label))
997                        {
998                            text = new I18nizableText(label);
999                        }
1000                    }
1001                }
1002            }
1003        }
1004        return text;
1005    }
1006
1007    private String _generateContentTypeIdKey(String contentTypeId)
1008    {
1009        String key = contentTypeId.replaceFirst("content-type.", "");
1010        key = key.replaceAll("\\s", "_");
1011        key = key.replaceAll("[^\\p{L}&&[^0-9]]", "_");
1012        return key;
1013    }
1014
1015    @SuppressWarnings("unchecked")
1016    private String _generateCategoryI18nKey(String pluginName, Map<String, Object> recoveredCategory)
1017    {
1018        String key = "";
1019        Map<String, String> values = (Map<String, String>) recoveredCategory.get("values");
1020        if (values != null && !values.isEmpty())
1021        {
1022            String categoryLabel = "";
1023            if (values.containsKey("en"))
1024            {
1025                categoryLabel = values.get("en");
1026
1027            }
1028            else
1029            {
1030                List<String> valuesAsList = new ArrayList(values.values());
1031                categoryLabel = valuesAsList.get(0);
1032            }
1033            key = "plugins_" + pluginName + "_" + "content_createcontentmenu_group" + "_" + _generateContentTypeIdKey(categoryLabel);
1034            key = key.toUpperCase();
1035        }
1036        return key;
1037    }
1038
1039    private String _generateI18nKey(String pluginName, String type, String contentTypeId, String label)
1040    {
1041        String id = _generateContentTypeIdKey(contentTypeId);
1042        String key = "plugins_" + pluginName + "_" + type + "_" + id + "_" + label;
1043        key = key.toUpperCase();
1044        return key;
1045    }
1046
1047    private Map<String, Map<String, String>> _createNewI18nCatalogs()
1048    {
1049        Map<String, Map<String, String>> i18nMessageTranslations = new HashMap<>();
1050
1051        for (TranslatedValue translatedValue : _translatedValues)
1052        {
1053            if (translatedValue.getTranslations() != null)
1054            {
1055                for (Entry<String, String> translation : translatedValue.getTranslations().entrySet())
1056                {
1057                    String i18nKey = translatedValue.getKey();
1058                    if (StringUtils.isNotBlank(i18nKey) && StringUtils.isNotBlank(translation.getValue()))
1059                    {
1060                        String language = translation.getKey();
1061                        if (i18nMessageTranslations.containsKey(language))
1062                        {
1063                            Map<String, String> map = i18nMessageTranslations.get(language);
1064                            map.put(i18nKey, translation.getValue());
1065                        }
1066                        else
1067                        {
1068                            Map<String, String> map = new HashMap<>();
1069                            map.put(i18nKey, translation.getValue());
1070                            i18nMessageTranslations.put(language, map);
1071                        }
1072                    }
1073                }
1074            }
1075        }
1076        return i18nMessageTranslations;
1077    }
1078
1079    private void _saxCatalogs(Map<String, Map<String, String>> newI18nMessages) throws Exception
1080    {
1081        // Determine the default catalog language
1082        String defaultLanguage = "";
1083        String defaultI18nCatalogPath = __I18N_CATALOG_DIR + "application.xml";
1084        Source defaultI18nCatalogSource = null;
1085        try
1086        {
1087            defaultI18nCatalogSource = _sourceResolver.resolveURI(defaultI18nCatalogPath);
1088            if (defaultI18nCatalogSource.exists())
1089            {
1090                // Parse default catalog application to know the default
1091                // language
1092                Configuration configuration = new DefaultConfigurationBuilder(true).build(defaultI18nCatalogSource.getInputStream(), "");
1093                defaultLanguage = configuration.getAttribute("xml:lang");
1094            }
1095        }
1096        catch (SourceNotFoundException e)
1097        {
1098            // Silently ignore
1099        }
1100        finally
1101        {
1102            _sourceResolver.release(defaultI18nCatalogSource);
1103        }
1104
1105        for (Entry<String, Map<String, String>> i18nMessageTranslation : newI18nMessages.entrySet())
1106        {
1107            Map<String, String> i18nMessages = new HashMap<>();
1108            String language = i18nMessageTranslation.getKey();
1109            String catalogPath;
1110            if (language.equals(defaultLanguage))
1111            {
1112                catalogPath = __I18N_CATALOG_DIR + "application.xml";
1113                i18nMessages = _readI18nCatalog(catalogPath);
1114            }
1115            else
1116            {
1117                catalogPath = __I18N_CATALOG_DIR + "application_" + language + ".xml";
1118                i18nMessages = _readI18nCatalog(catalogPath);
1119            }
1120            Map<String, String> updatedI18nMessages = _updateI18nMessages(i18nMessages, i18nMessageTranslation.getValue());
1121            _saveI18nCalatog(updatedI18nMessages, catalogPath, language);
1122        }
1123    }
1124
1125    private Map<String, String> _readI18nCatalog(String path) throws Exception
1126    {
1127        Map<String, String> i18nMessages = new LinkedHashMap<>();
1128        Source i18nCatalogSource = null;
1129
1130        try
1131        {
1132            i18nCatalogSource = _sourceResolver.resolveURI(path);
1133            if (i18nCatalogSource.exists())
1134            {
1135                if (_i18nCatalogs.containsKey(path))
1136                {
1137                    I18nCatalog i18nCatalog = _i18nCatalogs.get(path);
1138                    if (i18nCatalog.getLastModified() < i18nCatalogSource.getLastModified())
1139                    {
1140                        SAXParserFactory.newInstance().newSAXParser().parse(i18nCatalogSource.getInputStream(), new I18nMessageHandler(i18nMessages));
1141                        i18nCatalog.setI18nMessages(i18nMessages);
1142                    }
1143                    else
1144                    {
1145                        i18nMessages = i18nCatalog.getI18nMessages();
1146                    }
1147                }
1148                else
1149                {
1150                    SAXParserFactory.newInstance().newSAXParser().parse(i18nCatalogSource.getInputStream(), new I18nMessageHandler(i18nMessages));
1151                    I18nCatalog i18nCatalog = new I18nCatalog(i18nMessages);
1152                    _i18nCatalogs.put(path, i18nCatalog);
1153                }
1154            }
1155        }
1156        catch (SourceNotFoundException e)
1157        {
1158            // Silently ignore
1159        }
1160        finally
1161        {
1162            _sourceResolver.release(i18nCatalogSource);
1163        }
1164
1165        return i18nMessages;
1166    }
1167
1168    private Map<String, String> _updateI18nMessages(Map<String, String> i18nMessages, Map<String, String> newI18nMessages)
1169    {
1170        for (Entry<String, String> newI18nMessage : newI18nMessages.entrySet())
1171        {
1172            String key = newI18nMessage.getKey();
1173            String value = newI18nMessage.getValue();
1174            if (i18nMessages.containsKey(key))
1175            {
1176                i18nMessages.replace(key, value);
1177            }
1178            else
1179            {
1180                i18nMessages.put(key, value);
1181            }
1182        }
1183        return i18nMessages;
1184    }
1185
1186    private void _saveI18nCalatog(Map<String, String> i18nMessages, String catalogPath, String language) throws Exception
1187    {
1188        ModifiableSource defaultI18nCatalogSource = null;
1189        try
1190        {
1191            defaultI18nCatalogSource = (ModifiableSource) _sourceResolver.resolveURI(catalogPath);
1192            try (OutputStream os = defaultI18nCatalogSource.getOutputStream())
1193            {
1194                // create a transformer for saving sax into a file
1195                TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler();
1196
1197                StreamResult result = new StreamResult(os);
1198                th.setResult(result);
1199
1200                // create the format of result
1201                Properties format = new Properties();
1202                format.put(OutputKeys.METHOD, "xml");
1203                format.put(OutputKeys.INDENT, "yes");
1204                format.put(OutputKeys.ENCODING, "UTF-8");
1205                format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "2");
1206                th.getTransformer().setOutputProperties(format);
1207
1208                // sax the config into the transformer
1209                _saxI18nCatalog(th, i18nMessages, language);
1210
1211            }
1212            catch (Exception e)
1213            {
1214                throw new Exception("An error occured while saving the catalog values.", e);
1215            }
1216            _i18nCatalogs.get(catalogPath).setLastModified(defaultI18nCatalogSource.getLastModified());
1217        }
1218        catch (SourceNotFoundException e)
1219        {
1220            // Silently ignore
1221        }
1222        finally
1223        {
1224            _sourceResolver.release(defaultI18nCatalogSource);
1225        }
1226
1227    }
1228
1229    private void _saxI18nCatalog(TransformerHandler handler, Map<String, String> i18nMessages, String language) throws SAXException
1230    {
1231        handler.startDocument();
1232        AttributesImpl attribute = new AttributesImpl();
1233        attribute.addCDATAAttribute("xml:lang", language);
1234        XMLUtils.startElement(handler, "catalogue", attribute);
1235        for (Entry<String, String> i18Message : i18nMessages.entrySet())
1236        {
1237            attribute = new AttributesImpl();
1238            attribute.addCDATAAttribute("key", i18Message.getKey());
1239            XMLUtils.createElement(handler, "message", attribute, i18Message.getValue());
1240        }
1241        XMLUtils.endElement(handler, "catalogue");
1242        handler.endDocument();
1243    }
1244
1245    private void _saxRightsParam(String id, String label, String description) throws MalformedURLException, IOException
1246    {
1247        // For sax right params, a pipeline is used because it's more simple to copy a part of the existing right file to add or modify a right
1248        String uri = __SAVE_CONTENT_TYPE_RIGHT + "?id=" + id + "&label=" + label + "&description=" + description + "&category=" + __RIGHT_CATEGORY + "&file-path=" + __RIGHTS_FILE;
1249        Source source = null;
1250        ModifiableSource fileSource = null;
1251
1252        try
1253        {
1254            source = _sourceResolver.resolveURI(uri);
1255            fileSource = (ModifiableSource) _sourceResolver.resolveURI(__RIGHTS_FILE);
1256            try (OutputStream resultOs = fileSource.getOutputStream(); InputStream sourceIs = source.getInputStream())
1257            {
1258                SourceUtil.copy(sourceIs, resultOs);
1259            }
1260        }
1261        finally
1262        {
1263            _sourceResolver.release(source);
1264            _sourceResolver.release(fileSource);
1265        }
1266    }
1267
1268}