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 */
016package org.ametys.plugins.thesaurus;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.collections.ListUtils;
029import org.apache.commons.lang3.ArrayUtils;
030import org.apache.commons.lang3.StringUtils;
031
032import org.ametys.cms.ObservationConstants;
033import org.ametys.cms.contenttype.ContentAttributeDefinition;
034import org.ametys.cms.contenttype.ContentType;
035import org.ametys.cms.contenttype.ContentTypeDefinition;
036import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
037import org.ametys.cms.contenttype.ContentTypesHelper;
038import org.ametys.cms.contenttype.EditContentTypeException;
039import org.ametys.cms.contenttype.EditContentTypeHelper;
040import org.ametys.cms.contenttype.RemoveContentTypeException;
041import org.ametys.cms.data.type.ModelItemTypeConstants;
042import org.ametys.cms.data.type.ModelItemTypeExtensionPoint;
043import org.ametys.cms.repository.Content;
044import org.ametys.cms.repository.ContentDAO;
045import org.ametys.cms.repository.ContentQueryHelper;
046import org.ametys.cms.repository.ContentTypeExpression;
047import org.ametys.core.observation.Event;
048import org.ametys.core.observation.ObservationManager;
049import org.ametys.core.right.RightManager;
050import org.ametys.core.right.RightManager.RightResult;
051import org.ametys.core.ui.Callable;
052import org.ametys.core.user.CurrentUserProvider;
053import org.ametys.plugins.repository.AmetysObjectIterable;
054import org.ametys.plugins.repository.AmetysObjectResolver;
055import org.ametys.plugins.repository.AmetysRepositoryException;
056import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
057import org.ametys.plugins.repository.RemovableAmetysObject;
058import org.ametys.plugins.repository.RepositoryConstants;
059import org.ametys.plugins.repository.jcr.NameHelper;
060import org.ametys.plugins.repository.query.expression.AndExpression;
061import org.ametys.plugins.repository.query.expression.Expression;
062import org.ametys.plugins.repository.query.expression.Expression.Operator;
063import org.ametys.plugins.repository.query.expression.MetadataExpression;
064import org.ametys.plugins.repository.query.expression.NotExpression;
065import org.ametys.plugins.repository.query.expression.StringExpression;
066import org.ametys.runtime.i18n.I18nizableText;
067import org.ametys.runtime.model.ModelItem;
068import org.ametys.runtime.model.type.ModelItemType;
069import org.ametys.runtime.plugin.component.AbstractLogEnabled;
070import org.ametys.runtime.plugin.component.PluginAware;
071
072/**
073 * DAO for manipulating thesaurus
074 *
075 */
076public class ThesaurusDAO extends AbstractLogEnabled implements Serviceable, Component, PluginAware
077{
078    /** The Avalon role */
079    public static final String ROLE = ThesaurusDAO.class.getName();
080    
081    /** Metadata for related terms */
082    public static final String MICROTHESAURUS_CONTENT_TYPE_PREFIX = "content-type.org.ametys.plugins.thesaurus.Content.term.";
083    
084    /** Id of all microthesaurus super content type*/
085    public static final String MICROTHESAURUS_ABSTRACT_CONTENT_TYPE = "org.ametys.plugins.thesaurus.Content.term";
086    
087    /** Metadata for related terms */
088    public static final String METADATA_RELATED_TERMS = "relatedTerms";
089    /** Metadata for specific terms */
090    public static final String METADATA_SPECIFIC_TERMS = "specificTerms";
091    /** Metadata for generic term */
092    public static final String METADATA_GENERIC_TERM = "genericTerm";
093    /** Metadata for synonyms */
094    public static final String METADATA_SYNONYMS = "synonyms";
095    /** Metadata for applicationNote */
096    public static final String METADATA_APPLICATION_NOTE = "applicationNote";
097    /** Metadata for explanatoryNote */
098    public static final String METADATA_EXPLANATORY_NOTE = "explanatoryNote";
099    
100    private static final String __PLUGIN_NODE_NAME = "thesaurus";
101    private static final String __ROOT_THESAURII_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":thesaurii";
102    
103    /** Ametys resolver */
104    protected AmetysObjectResolver _resolver;
105    /** The right manager */
106    protected RightManager _rightManager;
107    /** The current user provider */
108    protected CurrentUserProvider _currentUserProvider;
109    /** The extension point for content types */
110    protected ContentTypeExtensionPoint _contentTypeEP;
111    /** The content types helper */
112    protected ContentTypesHelper _contentTypesHelper;
113    /** The extension point for available attribute types */
114    protected ModelItemTypeExtensionPoint _contentAttributeTypeExtensionPoint;
115    /** The contentDAO */
116    protected ContentDAO _contentDAO;
117    /** The EditContentTypeHelper */
118    protected EditContentTypeHelper _editContentTypeHelper;
119    /** Ametys observation manger */
120    protected ObservationManager _observationManager;
121
122    private String _pluginName;
123    
124    @Override
125    public void service(ServiceManager manager) throws ServiceException
126    {
127        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
128        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
129        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
130        _contentTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
131        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
132        _contentAttributeTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_CONTENT_ATTRIBUTE);
133        _contentDAO = (ContentDAO) manager.lookup(ContentDAO.ROLE);
134        _editContentTypeHelper = (EditContentTypeHelper) manager.lookup(EditContentTypeHelper.ROLE);
135        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
136    }
137    
138    public void setPluginInfo(String pluginName, String featureName, String id)
139    {
140        _pluginName = pluginName;
141    }
142    
143    /**
144     * Get the list of thesaurus
145     * @return the thesaurus
146     */
147    public AmetysObjectIterable<Thesaurus> getThesaurii()
148    {
149        ModifiableTraversableAmetysObject rootNode = getRootNode();
150        return rootNode.getChildren();
151    }
152    
153    /**
154     * Get microthesaurii of a thesaurus
155     * @param thesaurusId The id of thesaurus
156     * @return the contentType ids of microthesaurii
157     */
158    @Callable(rights = "Thesaurus_Rights_AccessThesaurus", context = "/cms")
159    public List<String> getMicrothesaurii (String thesaurusId)
160    {
161        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
162        return thesaurus.getMicrothesaurii();
163    }
164    
165    /**
166     * Get the list of ids of all the microthesaurii
167     * @return the list if microthesaurii ids
168     */
169    public List<String> getMicrothesaurii()
170    {
171        List<String> microthesauriiList = new ArrayList<>();
172        AmetysObjectIterable<Thesaurus> thesaurii = getThesaurii();
173        for (Thesaurus thesaurus: thesaurii)
174        {
175            List<String> microthesaurii = getMicrothesaurii(thesaurus.getId());
176            microthesauriiList.addAll(microthesaurii);
177        }
178        
179        return microthesauriiList;
180    }
181
182    /**
183     * Get the parent thesaurus
184     * @param microThesaurus The microthesaurus
185     * @return the parent thesaurus
186     */
187    public Thesaurus getParentThesaurus (ContentType microThesaurus)
188    {
189        AmetysObjectIterable<Thesaurus> thesaurii = getThesaurii();
190        for (Thesaurus thesaurus : thesaurii)
191        {
192            if (thesaurus.getMicrothesaurii().contains(microThesaurus.getId()))
193            {
194                return thesaurus;
195            }
196        }
197        
198        return null;
199    }
200    
201    /**
202     * Get the contentTypeDefinition for a microthesaurus
203     * @param cTypeId The id of content type for this microthesaurus
204     * @param label The label of the microthesaurus
205     * @param parentThesaurus The parent thesaurus
206     * @return The ContentTypeDefinition of the reference table content type
207     */
208    private ContentTypeDefinition _getMicrothesaurusContentTypeDefinition(String cTypeId, String label, Thesaurus parentThesaurus)
209    {
210        ContentTypeDefinition cTypeDef = new ContentTypeDefinition(cTypeId);
211        
212        cTypeDef.setPluginName(_pluginName);
213        cTypeDef.setLabel(new I18nizableText(label));
214        cTypeDef.setDescription(new I18nizableText(label));
215        cTypeDef.setDefaultTitle(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_DEFAULT_TITLE"));
216        cTypeDef.setCategory(new I18nizableText(parentThesaurus.getLabel()));
217        cTypeDef.setIconGlyph("ametysicon-letter-a");
218        cTypeDef.setSupertypeIds(new String[] {MICROTHESAURUS_ABSTRACT_CONTENT_TYPE});
219        
220        // Set tags
221        cTypeDef.setTagsInherited(true);
222        
223        // Additional model items
224        List<ModelItem> modelItems = _getModelItems(cTypeId);
225        cTypeDef.setModelItems(modelItems);
226        cTypeDef.setParentRef(METADATA_GENERIC_TERM);
227
228        // Do not override metadatasets from the supertypTerm
229        cTypeDef.setViews(ListUtils.EMPTY_LIST);
230        
231        return cTypeDef;
232    }
233    
234    /**
235     * Get the additional metadata for a microthesaurus
236     * @param microthesaurusCTypeId The id of content type for this microthesaurus
237     * @return The list of additional metadata definition
238     */
239    private List<ModelItem> _getModelItems(String microthesaurusCTypeId)
240    {
241        List<ModelItem> modelItems = new ArrayList<>();
242        ModelItemType contentAttributeType = _contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID);
243        
244        // Generic term
245        ContentAttributeDefinition genericTerm = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
246        genericTerm.setContentTypeId(microthesaurusCTypeId);
247        genericTerm.setName(METADATA_GENERIC_TERM);
248        genericTerm.setMultiple(false);
249        genericTerm.setType(contentAttributeType);
250        genericTerm.setInvertRelationPath(METADATA_SPECIFIC_TERMS);
251        genericTerm.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_GENERIC_TERMS"));
252        genericTerm.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_GENERIC_TERMS_DESC"));
253        modelItems.add(genericTerm);
254        
255        // Specific terms
256        ContentAttributeDefinition specificTerms = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
257        specificTerms.setContentTypeId(microthesaurusCTypeId);
258        specificTerms.setName(METADATA_SPECIFIC_TERMS);
259        specificTerms.setMultiple(true);
260        specificTerms.setType(contentAttributeType);
261        specificTerms.setInvertRelationPath(METADATA_GENERIC_TERM);
262        specificTerms.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_SPECIFIC_TERMS"));
263        specificTerms.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_SPECIFIC_TERMS_DESC"));
264        modelItems.add(specificTerms);
265        
266        // Related terms
267        ContentAttributeDefinition relatedTerms = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
268        relatedTerms.setContentTypeId(microthesaurusCTypeId);
269        relatedTerms.setName(METADATA_RELATED_TERMS);
270        relatedTerms.setMultiple(true);
271        relatedTerms.setType(contentAttributeType);
272        relatedTerms.setInvertRelationPath(METADATA_RELATED_TERMS);
273        relatedTerms.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_RELATED_TERMS"));
274        relatedTerms.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_RELATED_TERMS_DESC"));
275        modelItems.add(relatedTerms);
276        
277        return modelItems;
278    }
279
280    /**
281     * Get a thesaurus by its label
282     * @param label the label
283     * @return the thesaurus or null if not found
284     */
285    public Thesaurus findThesaurusByLabel(String label)
286    {
287        String xpathQuery = "//element(*, " + ThesaurusFactory.THESAURUS_NODETYPE + ")[@ametys-internal:label='" + label + "']";
288        
289        AmetysObjectIterable<Thesaurus> thesaurii = _resolver.query(xpathQuery);
290        return thesaurii.getSize() > 0 ? thesaurii.iterator().next() : null;
291    }
292    
293    /**
294     * Creates a thesaurus
295     * @param label The label
296     * @return The id and label of the created microthesaurus
297     */
298    @Callable(rights = "Thesaurus_Rights_CreateThesaurus", context = "/cms")
299    public Map<String, Object> createThesaurus (String label)
300    {
301        Map<String, Object> result = new HashMap<>();
302        
303        if (findThesaurusByLabel(label) != null)
304        {
305            result.put("success", false);
306            result.put("alreadyExist", true);
307            result.put("label", label);
308            return result;
309        }
310        
311        ModifiableTraversableAmetysObject rootNode = getRootNode();
312        String originalName = NameHelper.filterName(label);
313        
314        // Find unique name
315        int index = 2;
316        String name = originalName;
317        while (rootNode.hasChild(name))
318        {
319            name = originalName + "_" + (index++);
320        }
321        
322        Thesaurus thesaurus = rootNode.createChild(name, ThesaurusFactory.THESAURUS_NODETYPE);
323        thesaurus.setLabel(label);
324        
325        rootNode.saveChanges();
326        
327        result.put("success", true);
328        result.put("id", thesaurus.getId());
329        result.put("label", thesaurus.getLabel());
330        result.put("canCreateMicroThesaurus", _rightManager.currentUserHasRight("Thesaurus_Rights_EditMicrothesaurus", "/cms") == RightResult.RIGHT_ALLOW);
331        
332        return result;
333    }
334    
335    /**
336     * Updates a thesaurus
337     * @param thesaurusId The id of microthesaurus
338     * @param label The new label
339     * @return The id and label of the updated microthesaurus
340     * @throws EditContentTypeException if failed to update the existing content types for child microthesaurii.
341     */
342    @Callable(rights = "Thesaurus_Rights_EditThesaurus", context = "/cms")
343    public Map<String, Object> updateThesaurus (String thesaurusId, String label) throws EditContentTypeException
344    {
345        Map<String, Object> result = new HashMap<>();
346        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
347        
348        Thesaurus homonym = findThesaurusByLabel(label);
349        if (homonym != null && !homonym.equals(thesaurus))
350        {
351            result.put("success", false);
352            result.put("alreadyExist", true);
353            result.put("label", label);
354            return result;
355        }
356        
357        // Update category of existing microthesaurii content type
358        Collection<String> microthesaurii = thesaurus.getMicrothesaurii();
359        if (microthesaurii.size() > 0)
360        {
361            result.put("hasMicrothesaurii", true);
362            for (String microthesaurus: microthesaurii)
363            {
364                ContentType contentType = _contentTypeEP.getExtension(microthesaurus);
365                
366                ContentTypeDefinition contentTypeDef = _getMicrothesaurusContentTypeDefinition(contentType.getId(), contentType.getLabel().getLabel(), thesaurus);
367                contentTypeDef.setCategory(new I18nizableText(label));
368                _editContentTypeHelper.editContentType(contentTypeDef);
369            }
370        }
371        
372        thesaurus.setLabel(label);
373        thesaurus.saveChanges();
374        
375        result.put("success", true);
376        result.put("id", thesaurus.getId());
377        result.put("label", thesaurus.getLabel());
378        
379        return result;
380    }
381    
382    /**
383     * Deletes a thesaurus
384     * @param thesaurusId The id of thesaurus
385     * @return The id of the deleted thesaurus
386     */
387    @Callable(rights = "Thesaurus_Rights_EditThesaurus", context = "/cms")
388    public Map<String, Object> deleteThesaurus (String thesaurusId)
389    {
390        Map<String, Object> result = new HashMap<>();
391        
392        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
393        List<String> microthesaurii = thesaurus.getMicrothesaurii();
394        
395        for (String microthesaurusId : microthesaurii)
396        {
397            Map<String, Object> checkResult = checkBeforeMicrothesaurusDeletion(microthesaurusId);
398            boolean checkMicrothesaurusOk = (boolean) checkResult.get("success");
399            if (!checkMicrothesaurusOk)
400            {
401                result.putAll(checkResult);
402                
403                ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
404                
405                Map<String, Object> microthesaurusParams = new HashMap<>();
406                microthesaurusParams.put("id", microthesaurusId);
407                microthesaurusParams.put("label", cType.getLabel());
408                
409                result.put("undeletable-microthesaurus", microthesaurusParams);
410                return result;
411            }
412        }
413        
414        
415        result.put("id", thesaurus.getId());
416        result.put("deleted-microthesaurii", new ArrayList<>());
417        
418        // Delete microthesaurus first
419        for (String microthesaurusId : microthesaurii)
420        {
421            ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
422            
423            Map<String, Object> microthesaurusParams = new HashMap<>();
424            microthesaurusParams.put("id", microthesaurusId);
425            microthesaurusParams.put("label", cType.getLabel());
426            
427            Map<String, Object> deletionResult = deleteMicrothesaurus(microthesaurusId);
428            boolean deletionOk = (boolean) deletionResult.get("success");
429            
430            if (!deletionOk)
431            {
432                result.put("success", false);
433                result.put("undeleted-microthesaurus", microthesaurusParams);
434                return result;
435            }
436            else
437            {
438                @SuppressWarnings("unchecked")
439                List<Map<String, Object>> deletedMT = (List<Map<String, Object>>) result.get("deleted-microthesaurii");
440                deletedMT.add(microthesaurusParams);
441            }
442        }
443
444        // Delete thesaurus
445        thesaurus.remove();
446        thesaurus.saveChanges();
447        
448        result.put("success", true);
449        return result;
450    }
451    
452    /**
453     * Get the label of thesaurus
454     * @param thesaurusId The id of thesaurus
455     * @return The id and label of the thesaurus
456     */
457    @Callable(rights = "Thesaurus_Rights_AccessThesaurus", context = "/cms")
458    public String getThesaurusLabel (String thesaurusId)
459    {
460        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
461        return thesaurus.getLabel();
462    }
463    
464    /**
465     * Get the all terms of a microthesaurus
466     * @param microthesaurusId the id of microthesaurus
467     * @return all terms
468     */
469    public AmetysObjectIterable<Content> getAllTerms(String microthesaurusId)
470    {
471        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
472        String xPathQuery = ContentQueryHelper.getContentXPathQuery(cTypeExpr);
473        
474        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
475        return terms;
476    }
477    
478    /**
479     * Get the root terms of a microthesaurus
480     * @param microthesaurusId the id of microthesaurus
481     * @return root terms
482     */
483    public AmetysObjectIterable<Content> getRootTerms(String microthesaurusId)
484    {
485        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
486        Expression genericExpr = new NotExpression(new MetadataExpression(METADATA_GENERIC_TERM));
487        String xPathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, genericExpr));
488        
489        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
490        return terms;
491    }
492    
493    /**
494     * Get direct child terms of a term
495     * @param microthesaurusId the id of microthesaurus
496     * @param parentId The parent Id
497     * @return The child terms
498     */
499    public AmetysObjectIterable<Content> getChildTerms(String microthesaurusId, String parentId)
500    {
501        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
502        Expression genericExpr = new StringExpression(METADATA_GENERIC_TERM, Operator.EQ, parentId);
503        String xPathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, genericExpr));
504        
505        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
506        return terms;
507    }
508    
509    /**
510     * Determines if a term which belongs to a microthesaurus is referenced by contents outside the microthesaurus
511     * @param microthesaurusId the id of microthesaurus
512     * @param term the content term
513     * @return true if the term has at least one referencing content outside the microthesaurus
514     */
515    public boolean hasReferencingContents(String microthesaurusId, Content term)
516    {
517        Collection<Content> referencingContents = term.getReferencingContents();
518        
519        for (Content referencingContent : referencingContents)
520        {
521            if (!ArrayUtils.contains(referencingContent.getTypes(), microthesaurusId))
522            {
523                return true;
524            }
525        }
526        
527        return false;
528    }
529    
530    /**
531     * Creates a microthesaurus
532     * @param label The label
533     * @param thesaurusId The id of parent thesaurus
534     * @param microthesaurusName the name of the microthesaurus
535     * @return The id and label of the created microthesaurus
536     * @throws EditContentTypeException if failed to create the microthesaurus content type
537     */
538    @Callable(rights = {"Thesaurus_Rights_EditMicrothesaurus"}, context = "/cms")
539    public Map<String, Object> createMicrothesaurus (String label, String thesaurusId, String microthesaurusName) throws EditContentTypeException
540    {
541        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
542        
543        Map<String, Object> result = new HashMap<>();
544        
545        String cTypeId = MICROTHESAURUS_CONTENT_TYPE_PREFIX + microthesaurusName;
546        
547        List<String> microthesaurii = getMicrothesaurii();
548        
549        // Check if microthesaurus does not already exist
550        if (microthesaurii.contains(cTypeId))
551        {
552            result.put("success", false);
553            result.put("alreadyExist", microthesaurusName);
554            
555            int index = 2;
556            while (microthesaurii.contains(cTypeId))
557            {
558                cTypeId = MICROTHESAURUS_CONTENT_TYPE_PREFIX + microthesaurusName + "-" + index;
559                index++;
560            }
561            
562            result.put("suggestedName", StringUtils.substringAfter(cTypeId, MICROTHESAURUS_CONTENT_TYPE_PREFIX));
563            return result;
564        }
565        
566        // Create content type
567        ContentTypeDefinition contentType = _getMicrothesaurusContentTypeDefinition(cTypeId, label, thesaurus);
568        _editContentTypeHelper.createContentType(contentType);
569        
570        thesaurus.addMicrothesaurus(cTypeId);
571        thesaurus.saveChanges();
572        
573        result.put("success", true);
574        result.put("id", microthesaurusName);
575        result.put("label", label);
576        result.put("thesaurusId", thesaurusId);
577        
578        return result;
579    }
580    
581    /**
582     * Edit a microthesaurus
583     * @param microthesaurusId The id of microthesaurus to edit
584     * @param label The new label
585     * @return The id and label of the updated microthesaurus
586     * @throws EditContentTypeException if failed to update the content type for this microthesaurus
587     */
588    @Callable(rights = "Thesaurus_Rights_EditMicrothesaurus", context = "/cms")
589    public Map<String, Object> updateMicrothesaurus (String microthesaurusId, String label) throws EditContentTypeException
590    {
591        ContentType microThesaurus = _contentTypeEP.getExtension(microthesaurusId);
592        
593        Thesaurus thesaurus = getParentThesaurus(microThesaurus);
594        
595        // Update content type
596        ContentTypeDefinition contentTypeDef = _getMicrothesaurusContentTypeDefinition(microThesaurus.getId(), label, thesaurus);
597        _editContentTypeHelper.editContentType(contentTypeDef);
598        
599        Map<String, Object> result = new HashMap<>();
600        result.put("id", microThesaurus.getId());
601        result.put("label", microThesaurus.getLabel());
602        result.put("thesaurusId", thesaurus.getId());
603        result.put("success", true);
604        
605        return result;
606    }
607    
608    /**
609     * Do some check before microthesaurus deletion
610     * @param microthesaurusId the id of microthesaurus
611     * @return the check result
612     */
613    public Map<String, Object> checkBeforeMicrothesaurusDeletion(String microthesaurusId)
614    {
615        Map<String, Object> result = new HashMap<>();
616        
617        AmetysObjectIterable<Content> terms = getAllTerms(microthesaurusId);
618        if (terms.getSize() == 0)
619        {
620            result.put("success", true);
621            result.put("emptyMicrothesaurus", true);
622            return result;
623        }
624        
625        for (Content term : terms)
626        {
627            if (hasReferencingContents(microthesaurusId, term))
628            {
629                result.put("success", false);
630                result.put("hasReferencingContents", true);
631                return result;
632            }
633        }
634        
635        result.put("success", true);
636        return result;
637    }
638    
639    /**
640     * Deletes a microthesaurus
641     * @param microthesaurusId The id of microthesaurus
642     * @return The id of the deleted microthesaurus
643     */
644    @Callable(rights = "Thesaurus_Rights_EditMicrothesaurus", context = "/cms")
645    public Map<String, Object> deleteMicrothesaurus(String microthesaurusId)
646    {
647        Map<String, Object> result = checkBeforeMicrothesaurusDeletion(microthesaurusId);
648        
649        boolean checkOk = (boolean) result.get("success");
650        if (!checkOk)
651        {
652            return result;
653        }
654        
655        // Remove existing terms
656        AmetysObjectIterable<Content> terms = getAllTerms(microthesaurusId);
657        for (Content term : terms)
658        {
659            _deleteTerm(term);
660        }
661        
662        ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
663        Thesaurus thesaurus = getParentThesaurus(cType);
664        
665        result.put("thesaurusId", thesaurus.getId());
666        result.put("id", microthesaurusId);
667        result.put("label", cType.getLabel());
668        
669        try
670        {
671            _editContentTypeHelper.removeContentType(microthesaurusId);
672        }
673        catch (RemoveContentTypeException e)
674        {
675            getLogger().error("The microthesaurus with id {} can not be removed", microthesaurusId, e);
676            result.put("success", false);
677            
678            return result;
679        }
680        
681        // Remove thesaurus from parent thesaurus
682        thesaurus.removeMicrothesaurus(microthesaurusId);
683        thesaurus.saveChanges();
684        
685        result.put("success", true);
686        
687        return result;
688    }
689      
690    private void _deleteTerm (Content term)
691    {
692        Map<String, Object> eventParams = new HashMap<>();
693        eventParams.put(ObservationConstants.ARGS_CONTENT, term);
694        eventParams.put(ObservationConstants.ARGS_CONTENT_NAME, term.getName());
695        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, term.getId());
696        
697        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETING, _currentUserProvider.getUser(), eventParams));
698        
699        RemovableAmetysObject removableContent = (RemovableAmetysObject) term;
700        
701        // Remove the content.
702        removableContent.remove();
703        
704        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETED, _currentUserProvider.getUser(), eventParams));
705    }
706    /**
707     * Get the root plugin storage object.
708     * @return the root plugin storage object.
709     * @throws AmetysRepositoryException if a repository error occurs.
710     */
711    public ModifiableTraversableAmetysObject getRootNode() throws AmetysRepositoryException
712    {
713        try
714        {
715            ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins");
716            
717            ModifiableTraversableAmetysObject pluginNode = _getOrCreateNode(pluginsNode, __PLUGIN_NODE_NAME, "ametys:unstructured");
718            
719            return _getOrCreateNode(pluginNode, "ametys:thesaurii", __ROOT_THESAURII_NODETYPE);
720        }
721        catch (AmetysRepositoryException e)
722        {
723            throw new AmetysRepositoryException("Unable to get the thesaurus root node", e);
724        }
725    }
726    
727    /**
728     * Get or create a node
729     * @param parentNode the parent node
730     * @param nodeName the name of the node
731     * @param nodeType the type of the node
732     * @return The retrieved or created node
733     * @throws AmetysRepositoryException if an error occurs when manipulating the repository
734     */
735    protected ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException
736    {
737        ModifiableTraversableAmetysObject definitionsNode;
738        if (parentNode.hasChild(nodeName))
739        {
740            definitionsNode = parentNode.getChild(nodeName);
741        }
742        else
743        {
744            definitionsNode = parentNode.createChild(nodeName, nodeType);
745            parentNode.saveChanges();
746        }
747        return definitionsNode;
748    }
749}