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
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    @Callable
170    public List<String> getMicrothesaurii()
171    {
172        List<String> microthesauriiList = new ArrayList<>();
173        AmetysObjectIterable<Thesaurus> thesaurii = getThesaurii();
174        for (Thesaurus thesaurus: thesaurii)
175        {
176            List<String> microthesaurii = getMicrothesaurii(thesaurus.getId());
177            microthesauriiList.addAll(microthesaurii);
178        }
179        
180        return microthesauriiList;
181    }
182
183    /**
184     * Get the parent thesaurus
185     * @param microThesaurus The microthesaurus
186     * @return the parent thesaurus
187     */
188    public Thesaurus getParentThesaurus (ContentType microThesaurus)
189    {
190        AmetysObjectIterable<Thesaurus> thesaurii = getThesaurii();
191        for (Thesaurus thesaurus : thesaurii)
192        {
193            if (thesaurus.getMicrothesaurii().contains(microThesaurus.getId()))
194            {
195                return thesaurus;
196            }
197        }
198        
199        return null;
200    }
201    
202    /**
203     * Get the contentTypeDefinition for a microthesaurus
204     * @param cTypeId The id of content type for this microthesaurus
205     * @param label The label of the microthesaurus
206     * @param parentThesaurus The parent thesaurus
207     * @return The ContentTypeDefinition of the reference table content type
208     */
209    private ContentTypeDefinition _getMicrothesaurusContentTypeDefinition(String cTypeId, String label, Thesaurus parentThesaurus)
210    {
211        ContentTypeDefinition cTypeDef = new ContentTypeDefinition(cTypeId);
212        
213        cTypeDef.setPluginName(_pluginName);
214        cTypeDef.setLabel(new I18nizableText(label));
215        cTypeDef.setDescription(new I18nizableText(label));
216        cTypeDef.setDefaultTitle(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_DEFAULT_TITLE"));
217        cTypeDef.setCategory(new I18nizableText(parentThesaurus.getLabel()));
218        cTypeDef.setIconGlyph("ametysicon-letter-a");
219        cTypeDef.setSupertypeIds(new String[] {MICROTHESAURUS_ABSTRACT_CONTENT_TYPE});
220        
221        // Set tags
222        cTypeDef.setTagsInherited(true);
223        
224        // Additional model items
225        List<ModelItem> modelItems = _getModelItems(cTypeId);
226        cTypeDef.setModelItems(modelItems);
227        cTypeDef.setParentRef(METADATA_GENERIC_TERM);
228
229        // Do not override metadatasets from the supertypTerm
230        cTypeDef.setViews(ListUtils.EMPTY_LIST);
231        
232        return cTypeDef;
233    }
234    
235    /**
236     * Get the additional metadata for a microthesaurus
237     * @param microthesaurusCTypeId The id of content type for this microthesaurus
238     * @return The list of additional metadata definition
239     */
240    private List<ModelItem> _getModelItems(String microthesaurusCTypeId)
241    {
242        List<ModelItem> modelItems = new ArrayList<>();
243        ModelItemType contentAttributeType = _contentAttributeTypeExtensionPoint.getExtension(ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID);
244        
245        // Generic term
246        ContentAttributeDefinition genericTerm = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
247        genericTerm.setContentTypeId(microthesaurusCTypeId);
248        genericTerm.setName(METADATA_GENERIC_TERM);
249        genericTerm.setMultiple(false);
250        genericTerm.setType(contentAttributeType);
251        genericTerm.setInvertRelationPath(METADATA_SPECIFIC_TERMS);
252        genericTerm.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_GENERIC_TERMS"));
253        genericTerm.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_GENERIC_TERMS_DESC"));
254        modelItems.add(genericTerm);
255        
256        // Specific terms
257        ContentAttributeDefinition specificTerms = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
258        specificTerms.setContentTypeId(microthesaurusCTypeId);
259        specificTerms.setName(METADATA_SPECIFIC_TERMS);
260        specificTerms.setMultiple(true);
261        specificTerms.setType(contentAttributeType);
262        specificTerms.setInvertRelationPath(METADATA_GENERIC_TERM);
263        specificTerms.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_SPECIFIC_TERMS"));
264        specificTerms.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_SPECIFIC_TERMS_DESC"));
265        modelItems.add(specificTerms);
266        
267        // Related terms
268        ContentAttributeDefinition relatedTerms = new ContentAttributeDefinition(_contentTypeEP, _contentTypesHelper);
269        relatedTerms.setContentTypeId(microthesaurusCTypeId);
270        relatedTerms.setName(METADATA_RELATED_TERMS);
271        relatedTerms.setMultiple(true);
272        relatedTerms.setType(contentAttributeType);
273        relatedTerms.setInvertRelationPath(METADATA_RELATED_TERMS);
274        relatedTerms.setLabel(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_RELATED_TERMS"));
275        relatedTerms.setDescription(new I18nizableText("plugin." + _pluginName, "PLUGINS_THESAURUS_CONTENT_TYPE_TERM_RELATED_TERMS_DESC"));
276        modelItems.add(relatedTerms);
277        
278        return modelItems;
279    }
280
281    /**
282     * Get a thesaurus by its label
283     * @param label the label
284     * @return the thesaurus or null if not found
285     */
286    public Thesaurus findThesaurusByLabel(String label)
287    {
288        String xpathQuery = "//element(*, " + ThesaurusFactory.THESAURUS_NODETYPE + ")[@ametys-internal:label='" + label + "']";
289        
290        AmetysObjectIterable<Thesaurus> thesaurii = _resolver.query(xpathQuery);
291        return thesaurii.getSize() > 0 ? thesaurii.iterator().next() : null;
292    }
293    
294    /**
295     * Creates a thesaurus
296     * @param label The label
297     * @return The id and label of the created microthesaurus
298     */
299    @Callable
300    public Map<String, Object> createThesaurus (String label)
301    {
302        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Thesaurus_Rights_CreateThesaurus", "/cms") != RightResult.RIGHT_ALLOW)
303        {
304            String errorMessage = "User " + _currentUserProvider.getUser() + " try to create thesaurus with no sufficient rights"; 
305            getLogger().error(errorMessage);
306            throw new IllegalStateException(errorMessage);
307        }   
308        
309        Map<String, Object> result = new HashMap<>();
310        
311        if (findThesaurusByLabel(label) != null)
312        {
313            result.put("success", false);
314            result.put("alreadyExist", true);
315            result.put("label", label);
316            return result;
317        }
318        
319        ModifiableTraversableAmetysObject rootNode = getRootNode();
320        String originalName = NameHelper.filterName(label);
321        
322        // Find unique name
323        int index = 2;
324        String name = originalName;
325        while (rootNode.hasChild(name))
326        {
327            name = originalName + "_" + (index++);
328        }
329        
330        Thesaurus thesaurus = rootNode.createChild(name, ThesaurusFactory.THESAURUS_NODETYPE);
331        thesaurus.setLabel(label);
332        
333        rootNode.saveChanges();
334        
335        result.put("success", true);
336        result.put("id", thesaurus.getId());
337        result.put("label", thesaurus.getLabel());
338        
339        return result;
340    }
341    
342    /**
343     * Updates a thesaurus
344     * @param thesaurusId The id of microthesaurus
345     * @param label The new label
346     * @return The id and label of the updated microthesaurus
347     * @throws EditContentTypeException if failed to update the existing content types for child microthesaurii.
348     */
349    @Callable
350    public Map<String, Object> updateThesaurus (String thesaurusId, String label) throws EditContentTypeException
351    {
352        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Thesaurus_Rights_EditThesaurus", "/cms") != RightResult.RIGHT_ALLOW)
353        {
354            String errorMessage = "User " + _currentUserProvider.getUser() + " try to edit thesaurus of id " + thesaurusId + " with no sufficient rights"; 
355            getLogger().error(errorMessage);
356            throw new IllegalStateException(errorMessage);
357        }   
358
359        Map<String, Object> result = new HashMap<>();
360        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
361        
362        Thesaurus homonym = findThesaurusByLabel(label);
363        if (homonym != null && !homonym.equals(thesaurus))
364        {
365            result.put("success", false);
366            result.put("alreadyExist", true);
367            result.put("label", label);
368            return result;
369        }
370        
371        // Update category of existing microthesaurii content type
372        Collection<String> microthesaurii = thesaurus.getMicrothesaurii();
373        if (microthesaurii.size() > 0)
374        {
375            result.put("hasMicrothesaurii", true);
376            for (String microthesaurus: microthesaurii)
377            {
378                ContentType contentType = _contentTypeEP.getExtension(microthesaurus);
379                
380                ContentTypeDefinition contentTypeDef = _getMicrothesaurusContentTypeDefinition(contentType.getId(), contentType.getLabel().getLabel(), thesaurus);
381                contentTypeDef.setCategory(new I18nizableText(label));
382                _editContentTypeHelper.editContentType(contentTypeDef);
383            }
384        }
385        
386        thesaurus.setLabel(label);
387        thesaurus.saveChanges();
388        
389        result.put("success", true);
390        result.put("id", thesaurus.getId());
391        result.put("label", thesaurus.getLabel());
392        
393        return result;
394    }
395    
396    /**
397     * Deletes a thesaurus
398     * @param thesaurusId The id of thesaurus
399     * @return The id of the deleted thesaurus
400     */
401    @Callable
402    public Map<String, Object> deleteThesaurus (String thesaurusId)
403    {
404        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Thesaurus_Rights_EditThesaurus", "/cms") != RightResult.RIGHT_ALLOW)
405        {
406            String errorMessage = "User " + _currentUserProvider.getUser() + " try to delete thesaurus of id " + thesaurusId + " with no sufficient rights"; 
407            getLogger().error(errorMessage);
408            throw new IllegalStateException(errorMessage);
409        }   
410        
411        Map<String, Object> result = new HashMap<>();
412        
413        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
414        List<String> microthesaurii = thesaurus.getMicrothesaurii();
415        
416        for (String microthesaurusId : microthesaurii)
417        {
418            Map<String, Object> checkResult = checkBeforeMicrothesaurusDeletion(microthesaurusId);
419            boolean checkMicrothesaurusOk = (boolean) checkResult.get("success");
420            if (!checkMicrothesaurusOk)
421            {
422                result.putAll(checkResult);
423                
424                ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
425                
426                Map<String, Object> microthesaurusParams = new HashMap<>();
427                microthesaurusParams.put("id", microthesaurusId);
428                microthesaurusParams.put("label", cType.getLabel());
429                
430                result.put("undeletable-microthesaurus", microthesaurusParams);
431                return result;
432            }
433        }
434        
435        
436        result.put("id", thesaurus.getId());
437        result.put("deleted-microthesaurii", new ArrayList<>());
438        
439        // Delete microthesaurus first
440        for (String microthesaurusId : microthesaurii)
441        {
442            ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
443            
444            Map<String, Object> microthesaurusParams = new HashMap<>();
445            microthesaurusParams.put("id", microthesaurusId);
446            microthesaurusParams.put("label", cType.getLabel());
447            
448            Map<String, Object> deletionResult = deleteMicrothesaurus(microthesaurusId);
449            boolean deletionOk = (boolean) deletionResult.get("success");
450            
451            if (!deletionOk)
452            {
453                result.put("success", false);
454                result.put("undeleted-microthesaurus", microthesaurusParams);
455                return result;
456            }
457            else
458            {
459                @SuppressWarnings("unchecked")
460                List<Map<String, Object>> deletedMT = (List<Map<String, Object>>) result.get("deleted-microthesaurii");
461                deletedMT.add(microthesaurusParams);
462            }
463        }
464
465        // Delete thesaurus
466        thesaurus.remove();
467        thesaurus.saveChanges();
468        
469        result.put("success", true);
470        return result;
471    }
472    
473    /**
474     * Get the label of thesaurus
475     * @param thesaurusId The id of thesaurus
476     * @return The id and label of the thesaurus
477     */
478    @Callable
479    public String getThesaurusLabel (String thesaurusId)
480    {
481        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
482        return thesaurus.getLabel();
483    }
484    
485    /**
486     * Get the all terms of a microthesaurus
487     * @param microthesaurusId the id of microthesaurus
488     * @return all terms
489     */
490    public AmetysObjectIterable<Content> getAllTerms(String microthesaurusId)
491    {
492        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
493        String xPathQuery = ContentQueryHelper.getContentXPathQuery(cTypeExpr);
494        
495        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
496        return terms;
497    }
498    
499    /**
500     * Get the root terms of a microthesaurus
501     * @param microthesaurusId the id of microthesaurus
502     * @return root terms
503     */
504    public AmetysObjectIterable<Content> getRootTerms(String microthesaurusId)
505    {
506        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
507        Expression genericExpr = new NotExpression(new MetadataExpression(METADATA_GENERIC_TERM));
508        String xPathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, genericExpr));
509        
510        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
511        return terms;
512    }
513    
514    /**
515     * Get direct child terms of a term
516     * @param microthesaurusId the id of microthesaurus
517     * @param parentId The parent Id
518     * @return The child terms
519     */
520    public AmetysObjectIterable<Content> getChildTerms(String microthesaurusId, String parentId)
521    {
522        Expression cTypeExpr = new ContentTypeExpression(Operator.EQ, microthesaurusId);
523        Expression genericExpr = new StringExpression(METADATA_GENERIC_TERM, Operator.EQ, parentId);
524        String xPathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, genericExpr));
525        
526        AmetysObjectIterable<Content> terms = _resolver.query(xPathQuery);
527        return terms;
528    }
529    
530    /**
531     * Determines if a term which belongs to a microthesaurus is referenced by contents outside the microthesaurus 
532     * @param microthesaurusId the id of microthesaurus
533     * @param term the content term
534     * @return true if the term has at least one referencing content outside the microthesaurus 
535     */
536    public boolean hasReferencingContents(String microthesaurusId, Content term)
537    {
538        Collection<Content> referencingContents = term.getReferencingContents();
539        
540        for (Content referencingContent : referencingContents)
541        {
542            if (!ArrayUtils.contains(referencingContent.getTypes(), microthesaurusId))
543            {
544                return true;
545            }
546        }
547        
548        return false;
549    }
550    
551    /**
552     * Creates a microthesaurus
553     * @param label The label
554     * @param thesaurusId The id of parent thesaurus
555     * @param microthesaurusName the name of the microthesaurus
556     * @return The id and label of the created microthesaurus
557     * @throws EditContentTypeException if failed to create the microthesaurus content type
558     */
559    @Callable
560    public Map<String, Object> createMicrothesaurus (String label, String thesaurusId, String microthesaurusName) throws EditContentTypeException
561    {
562        Thesaurus thesaurus = _resolver.resolveById(thesaurusId);
563        
564        Map<String, Object> result = new HashMap<>();
565        
566        String cTypeId = MICROTHESAURUS_CONTENT_TYPE_PREFIX + microthesaurusName;
567        
568        List<String> microthesaurii = getMicrothesaurii();
569        
570        // Check if microthesaurus does not already exist
571        if (microthesaurii.contains(cTypeId))
572        {
573            result.put("success", false);
574            result.put("alreadyExist", microthesaurusName);
575            
576            int index = 2;
577            while (microthesaurii.contains(cTypeId))
578            {
579                cTypeId = MICROTHESAURUS_CONTENT_TYPE_PREFIX + microthesaurusName + "-" + index;
580                index++;
581            }
582            
583            result.put("suggestedName", StringUtils.substringAfter(cTypeId, MICROTHESAURUS_CONTENT_TYPE_PREFIX));
584            return result;
585        }
586        
587        // Create content type
588        ContentTypeDefinition contentType = _getMicrothesaurusContentTypeDefinition(cTypeId, label, thesaurus);
589        _editContentTypeHelper.createContentType(contentType);
590        
591        thesaurus.addMicrothesaurus(cTypeId);
592        thesaurus.saveChanges();
593        
594        result.put("success", true);
595        result.put("id", microthesaurusName);
596        result.put("label", label);
597        result.put("thesaurusId", thesaurusId);
598        
599        return result;
600    }
601    
602    /**
603     * Edit a microthesaurus
604     * @param microthesaurusId The id of microthesaurus to edit
605     * @param label The new label
606     * @return The id and label of the updated microthesaurus
607     * @throws EditContentTypeException if failed to update the content type for this microthesaurus
608     */
609    @Callable
610    public Map<String, Object> updateMicrothesaurus (String microthesaurusId, String label) throws EditContentTypeException
611    {
612        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Thesaurus_Rights_EditMicrothesaurus", "/cms") != RightResult.RIGHT_ALLOW)
613        {
614            String errorMessage = "User " + _currentUserProvider.getUser() + " try to edit microthesaurus of id '" + microthesaurusId + "' with no sufficient rights"; 
615            getLogger().error(errorMessage);
616            throw new IllegalStateException(errorMessage);
617        }    
618        
619        ContentType microThesaurus = _contentTypeEP.getExtension(microthesaurusId);
620        
621        Thesaurus thesaurus = getParentThesaurus(microThesaurus);
622        
623        // Update content type
624        ContentTypeDefinition contentTypeDef = _getMicrothesaurusContentTypeDefinition(microThesaurus.getId(), label, thesaurus);
625        _editContentTypeHelper.editContentType(contentTypeDef);
626        
627        Map<String, Object> result = new HashMap<>();
628        result.put("id", microThesaurus.getId());
629        result.put("label", microThesaurus.getLabel());
630        result.put("thesaurusId", thesaurus.getId());
631        result.put("success", true);
632        
633        return result;
634    }
635    
636    /**
637     * Do some check before microthesaurus deletion
638     * @param microthesaurusId the id of microthesaurus
639     * @return the check result
640     */
641    @Callable
642    public Map<String, Object> checkBeforeMicrothesaurusDeletion(String microthesaurusId)
643    {
644        Map<String, Object> result = new HashMap<>();
645        
646        AmetysObjectIterable<Content> terms = getAllTerms(microthesaurusId);
647        if (terms.getSize() == 0)
648        {
649            result.put("success", true);
650            result.put("emptyMicrothesaurus", true);
651            return result;
652        }
653        
654        for (Content term : terms)
655        {
656            if (hasReferencingContents(microthesaurusId, term))
657            {
658                result.put("success", false);
659                result.put("hasReferencingContents", true);
660                return result;
661            }
662        }
663        
664        result.put("success", true);
665        return result;
666    }
667    
668    /**
669     * Deletes a microthesaurus
670     * @param microthesaurusId The id of microthesaurus
671     * @return The id of the deleted microthesaurus
672     */
673    @Callable
674    public Map<String, Object> deleteMicrothesaurus(String microthesaurusId)
675    {
676        if (_rightManager.hasRight(_currentUserProvider.getUser(), "Thesaurus_Rights_EditMicrothesaurus", "/cms") != RightResult.RIGHT_ALLOW)
677        {
678            String errorMessage = "User " + _currentUserProvider.getUser() + " try to delete microthesaurus of id '" + microthesaurusId + "' with no sufficient rights"; 
679            getLogger().error(errorMessage);
680            throw new IllegalStateException(errorMessage);
681        }  
682        
683        Map<String, Object> result = checkBeforeMicrothesaurusDeletion(microthesaurusId);
684        
685        boolean checkOk = (boolean) result.get("success");
686        if (!checkOk)
687        {
688            return result;
689        }
690        
691        // Remove existing terms
692        AmetysObjectIterable<Content> terms = getAllTerms(microthesaurusId);
693        for (Content term : terms)
694        {
695            _deleteTerm(term);
696        }
697        
698        ContentType cType = _contentTypeEP.getExtension(microthesaurusId);
699        Thesaurus thesaurus = getParentThesaurus(cType);
700        
701        result.put("thesaurusId", thesaurus.getId());
702        result.put("id", microthesaurusId);
703        result.put("label", cType.getLabel());
704        
705        try
706        {
707            _editContentTypeHelper.removeContentType(microthesaurusId);
708        }
709        catch (RemoveContentTypeException e)
710        {
711            getLogger().error("The microthesaurus with id {} can not be removed", microthesaurusId, e);
712            result.put("success", false);
713            
714            return result;
715        }
716        
717        // Remove thesaurus from parent thesaurus
718        thesaurus.removeMicrothesaurus(microthesaurusId);
719        thesaurus.saveChanges();
720        
721        result.put("success", true);
722        
723        return result;
724    }
725      
726    private void _deleteTerm (Content term)
727    {
728        Map<String, Object> eventParams = new HashMap<>();
729        eventParams.put(ObservationConstants.ARGS_CONTENT, term);
730        eventParams.put(ObservationConstants.ARGS_CONTENT_NAME, term.getName());
731        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, term.getId());
732        
733        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETING, _currentUserProvider.getUser(), eventParams));
734        
735        RemovableAmetysObject removableContent = (RemovableAmetysObject) term;
736        
737        // Remove the content.
738        removableContent.remove();
739        
740        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_DELETED, _currentUserProvider.getUser(), eventParams));
741    }
742    /**
743     * Get the root plugin storage object.
744     * @return the root plugin storage object.
745     * @throws AmetysRepositoryException if a repository error occurs.
746     */
747    public ModifiableTraversableAmetysObject getRootNode() throws AmetysRepositoryException
748    {
749        try
750        {
751            ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins");
752            
753            ModifiableTraversableAmetysObject pluginNode = _getOrCreateNode(pluginsNode, __PLUGIN_NODE_NAME, "ametys:unstructured");
754            
755            return _getOrCreateNode(pluginNode, "ametys:thesaurii", __ROOT_THESAURII_NODETYPE);
756        }
757        catch (AmetysRepositoryException e)
758        {
759            throw new AmetysRepositoryException("Unable to get the thesaurus root node", e);
760        }
761    }
762    
763    /**
764     * Get or create a node
765     * @param parentNode the parent node
766     * @param nodeName the name of the node
767     * @param nodeType the type of the node
768     * @return The retrieved or created node
769     * @throws AmetysRepositoryException if an error occurs when manipulating the repository
770     */
771    protected ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException
772    {
773        ModifiableTraversableAmetysObject definitionsNode;
774        if (parentNode.hasChild(nodeName))
775        {
776            definitionsNode = parentNode.getChild(nodeName);
777        }
778        else
779        {
780            definitionsNode = parentNode.createChild(nodeName, nodeType);
781            parentNode.saveChanges();
782        }
783        return definitionsNode;
784    }
785}