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