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