001/*
002 *  Copyright 2013 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.cms.tag.jcr;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import javax.jcr.RepositoryException;
026import javax.jcr.Session;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.logger.AbstractLogEnabled;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.ProcessingException;
034
035import org.ametys.cms.ObservationConstants;
036import org.ametys.cms.tag.Tag;
037import org.ametys.cms.tag.TagHelper;
038import org.ametys.cms.tag.TagProvider;
039import org.ametys.core.observation.Event;
040import org.ametys.core.observation.ObservationManager;
041import org.ametys.core.ui.Callable;
042import org.ametys.core.user.CurrentUserProvider;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
045import org.ametys.plugins.repository.UnknownAmetysObjectException;
046import org.ametys.plugins.repository.jcr.JCRAmetysObject;
047import org.ametys.plugins.repository.jcr.SimpleAmetysObject;
048
049/**
050 * Component for operations on JCR tags
051 */
052public abstract class AbstractJCRTagsDAO extends AbstractLogEnabled implements Serviceable, Component
053{
054    /** The ametys object resolver */
055    protected AmetysObjectResolver _resolver;
056    
057    /** Observer manager. */
058    protected ObservationManager _observationManager;
059    
060    /** The current user provider */
061    protected CurrentUserProvider _currentUserProvider;
062    
063    @Override
064    public void service(ServiceManager manager) throws ServiceException
065    {
066        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
067        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
068        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
069    }
070    
071    /**
072     * Create a new tag from client-side. User rights are checked.
073     * @param parentId The id of parent tag
074     * @param originalName The original name
075     * @param title The tag's title
076     * @param description The tag's description
077     * @param otherParameters the other parameters
078     * @param contextualParameters Contextual parameters transmitted by the environment.
079     * @return The result map
080     */
081    @Callable
082    public Map<String, Object> createTag (String parentId, String originalName, String title, String description, Map<String, Object> otherParameters, Map<String, Object> contextualParameters)
083    {
084        _checkUserRight();
085        
086        Map<String, Object> result = new HashMap<>();
087        
088        try
089        {
090            JCRTag jcrTag = addTag(parentId, originalName, title, description, otherParameters, contextualParameters);
091            
092            result.put("tagId", jcrTag.getId());
093            result.put("tagName", jcrTag.getName());
094            result.put("tagParentId", parentId);
095        }
096        catch (RepositoryException e)
097        {
098            getLogger().error("Unable to get JCR tag root node", e);
099            result.put("message", "unknown-parent-tag");
100        }
101        catch (UnknownAmetysObjectException e)
102        {
103            getLogger().error("The tag '" + parentId + "' does not exist anymore", e);
104            result.put("message", "unknown-parent-tag");
105        }
106        
107        return result;
108    }
109    
110    /**
111     * Create a new tag regardless of user rights.
112     * @param parentId The id of parent tag
113     * @param originalName The original name
114     * @param title The tag's title
115     * @param description The tag's description
116     * @param otherParameters the other parameters
117     * @param contextualParameters Contextual parameters transmitted by the environment.
118     * @return The created JCR tag
119     * @throws UnknownAmetysObjectException if the parent does not exist
120     * @throws RepositoryException if failed to create tag
121     */
122    public JCRTag addTag(String parentId, String originalName, String title, String description, Map<String, Object> otherParameters, Map<String, Object> contextualParameters) throws UnknownAmetysObjectException, RepositoryException
123    {
124        // Find an unique name
125        String name = _findUniqueName (originalName, contextualParameters);
126        
127        JCRTag jcrTag = _createJCRTag(parentId, name, title, description, otherParameters, contextualParameters);
128        
129        // Notify observers that the tag has been added.
130        Map<String, Object> eventParams = new HashMap<>();
131        eventParams.put(ObservationConstants.ARGS_TAG_ID, jcrTag.getId());
132        eventParams.put(ObservationConstants.ARGS_TAG_NAME, jcrTag.getName());
133        _observationManager.notify(new Event(ObservationConstants.EVENT_TAG_ADDED, _currentUserProvider.getUser(), eventParams));
134        
135        return jcrTag;
136    }
137    
138    /**
139     * Deletes a JCR tag
140     * @param tagId The tag's id
141     * @param contextualParameters Contextual parameters transmitted by the environment.
142     * @return the result map
143     */
144    @Callable
145    public Map<String, Object> deleteTag (String tagId, Map<String, Object> contextualParameters)
146    {
147        _checkUserRight();
148        
149        Map<String, Object> result = new HashMap<>();
150        
151        try
152        {
153            JCRTag jcrTag = _resolver.resolveById(tagId);
154            SimpleAmetysObject<?> parent = jcrTag.getParent();
155            
156            Map<String, Object> eventParams = new HashMap<>();
157            eventParams.put(ObservationConstants.ARGS_TAG_ID, jcrTag.getId());
158            eventParams.put(ObservationConstants.ARGS_TAG_NAME, jcrTag.getName());
159            
160            // descendant names for the event arguments
161            Tag tag = _getTagFromName(jcrTag.getName(), contextualParameters);
162            Collection<String> descendantNames = TagHelper.getDescendantNames(tag, true);
163            eventParams.put("tag.descendantnames", descendantNames);
164            
165            result.put("id", jcrTag.getId());
166            result.put("name", jcrTag.getName());
167            result.put("title", jcrTag.getTitle());
168            
169            jcrTag.remove();
170            parent.saveChanges();
171            
172            // Notify observers that the tag has been deleted.
173            _observationManager.notify(new Event(ObservationConstants.EVENT_TAG_DELETED, _currentUserProvider.getUser(), eventParams));
174        }
175        catch (UnknownAmetysObjectException e)
176        {
177            getLogger().error("Unable to delete tag : the tag '" + tagId + "' does not exist anymore", e);
178            result.put("message", "unknown-tag");
179        }
180        
181        return result;
182    }
183    
184    /**
185     * Updates a JCR tag
186     * @param tagId The tag's id
187     * @param title The tag's title
188     * @param description The tag's description
189     * @param otherParameters the other parameters
190     * @return The result map
191     */
192    @Callable
193    public Map<String, Object> updateTag (String tagId, String title, String description, Map<String, Object> otherParameters)
194    {
195        _checkUserRight();
196        
197        Map<String, Object> result = new HashMap<>();
198        
199        try
200        {
201            JCRTag jcrTag = _updateJCRTag(tagId, title, description, otherParameters);
202            
203            result.put("id", jcrTag.getId());
204            result.put("name", jcrTag.getName());
205            result.put("title", title);
206            
207            // Notify observers that the tag has been modified.
208            
209            Map<String, Object> eventParams = new HashMap<>();
210            eventParams.put(ObservationConstants.ARGS_TAG_ID, jcrTag.getId());
211            eventParams.put(ObservationConstants.ARGS_TAG_NAME, jcrTag.getName());
212            _observationManager.notify(new Event(ObservationConstants.EVENT_TAG_UPDATED, _currentUserProvider.getUser(), eventParams));
213        }
214        catch (UnknownAmetysObjectException e)
215        {
216            getLogger().error("Unable to update tag : the tag '" + tagId + "' does not exist anymore", e);
217            result.put("message", "unknown-tag");
218        }
219               
220        return result;
221    }
222    
223    /**
224     * Move a JCR tag
225     * @param targetId The tag where to move to
226     * @param ids The ids of tag to move
227     * @return the result map
228     * @throws ProcessingException If an error occurred
229     */
230    @Callable
231    public Map<String, Object> moveTags(String targetId, List<String> ids) throws ProcessingException
232    {
233        _checkUserRight();
234        
235        Map<String, Object> result = new HashMap<>();
236        List<Map<String, Object>> movedTags = new ArrayList<>();
237
238        JCRAmetysObject target = _resolver.resolveById(targetId);
239        String targetPath = "";
240        try
241        {
242            targetPath = target.getNode().getPath();
243
244            for (int i = 0; i < ids.size(); i++)
245            {
246                JCRAmetysObject tag = (JCRAmetysObject) _resolver.resolveById(ids.get(i));
247                if (!tag.getNode().getPath().equals(targetPath + "/" + tag.getNode().getName()))
248                {
249                    Map<String, Object> eventParams = new HashMap<>();
250                    eventParams.put("id", tag.getId());
251                    eventParams.put("oldPath", tag.getNode().getPath());
252                    eventParams.put("path", targetPath + "/" + tag.getNode().getName());
253                    
254                    Session session = tag.getNode().getSession();
255                    session.move(tag.getNode().getPath(), targetPath + "/" + tag.getNode().getName());
256                    
257                    session.save();
258                    
259                    Map<String, Object> tagProperties = new HashMap<>();
260                    tagProperties.put("id", tag.getId());
261                    tagProperties.put("name", tag.getName());
262                    movedTags.add(tagProperties);
263                    
264                    _observationManager.notify(new Event(ObservationConstants.EVENT_TAG_MOVED, _currentUserProvider.getUser(), eventParams));
265                }
266            }
267        }
268        catch (RepositoryException e)
269        {
270            getLogger().error("Unable to get JCR tag node path", e);
271            throw new ProcessingException("Unable to get JCR tag node path", e);
272        }
273        
274        if (movedTags.size() > 0)
275        {
276            result.put("movedTags", movedTags);
277        }
278        
279        result.put("target", targetId);
280    
281        return result;
282    }
283    
284    /**
285     * Get the root node for tags
286     * @param tagProviderId The tag provider id
287     * @param contextualParameters Contextual parameters transmitted by the environment.
288     * @return The root node in key "id"
289     * @throws ProcessingException If an error occurred in the repository
290     */
291    @Callable
292    public Map<String, Object> getTagRootNode (String tagProviderId, Map<String, Object> contextualParameters) throws ProcessingException
293    {
294        Map<String, Object> result = new HashMap<>();
295        
296        try
297        {
298            result.put("id", _getTagRootObject(tagProviderId, contextualParameters).getId());
299        }
300        catch (RepositoryException e)
301        {
302            getLogger().error("Unable to get JCR tag root node", e);
303            throw new ProcessingException("Unable to get JCR tag root node", e);
304        }
305        
306        return result;
307    }
308    
309    /**
310     * Find a JCR tag and return its data
311     * @param tagId The tag's id
312     * @return The result map
313     */
314    @Callable
315    public Map<String, Object> getTag(String tagId)
316    {
317        Map<String, Object> result = new HashMap<>();
318        
319        try
320        {
321            JCRTag jcrTag = _resolver.resolveById(tagId);
322            result = jcrTag.toJSON();
323            
324            if (jcrTag.getChildren().iterator().hasNext())
325            {
326                result.put("leaf", true);
327            }
328        }
329        catch (UnknownAmetysObjectException e)
330        {
331            getLogger().error("Unable to get tag : the tag '" + tagId + "' does not exist anymore", e);
332            result.put("message", "unknown-tag");
333        }
334        
335        return result;
336    }
337
338    /**
339     * Get the tag root node object
340     * @param tagProviderId The tag provider id
341     * @param contextualParameters Contextual parameters transmitted by the environment.
342     * @return The tag root node object
343     * @throws RepositoryException If an error occurred in the repository
344     */
345    public abstract ModifiableTraversableAmetysObject _getTagRootObject (String tagProviderId, Map<String, Object> contextualParameters) throws RepositoryException;
346    
347    /**
348     * Check if the user right to access the feature
349     * @throws IllegalStateException if the user has no right
350     */
351    protected abstract void _checkUserRight () throws IllegalStateException;
352    
353    /**
354     * Get the tag from the name
355     * @param name the name
356     * @param contextualParameters the contextual parameters
357     * @return the tag
358     */
359    protected abstract Tag _getTagFromName(String name, Map<String, Object> contextualParameters);
360    
361    /**
362     * Get all tag's providers
363     * @return the providers
364     */
365    protected abstract Set<TagProvider<? extends Tag>> _getTagProviders ();
366    
367    /**
368     * Create a JCR tag under his parent
369     * @param parentId the parent id
370     * @param name the name
371     * @param title the title
372     * @param description the description
373     * @param otherParameters the other parameters
374     * @param contextualParameters Contextual parameters transmitted by the environment.
375     * @return the created JCR tag
376     * @throws RepositoryException if an error occurred
377     */
378    protected abstract JCRTag _createJCRTag(String parentId, String name, String title, String description, Map<String, Object> otherParameters, Map<String, Object> contextualParameters) throws RepositoryException;
379    
380    /**
381     * Update a JCR tag
382     * @param tagId the tag id to update
383     * @param title the title
384     * @param description the description
385     * @param otherParameters the other parameters
386     * @return return the updated JCR tag
387     * @throws UnknownAmetysObjectException if an error occurred
388     */
389    protected abstract JCRTag _updateJCRTag(String tagId, String title, String description, Map<String, Object> otherParameters) throws UnknownAmetysObjectException;
390    
391    
392    /**
393     * Find a unique name for the tag
394     * @param originalName The requested name
395     * @param contextualParameters Contextual parameters transmitted by the environment.
396     * @return A unique name
397     */
398    protected String _findUniqueName(String originalName, Map<String, Object> contextualParameters)
399    {
400        Set<TagProvider<? extends Tag>> providers = _getTagProviders();
401        
402        // Find an unique name
403        int index = 2;
404        String name = originalName;
405        while (_hasTag(providers, name, contextualParameters))
406        {
407            name = originalName + "_" + (index++);
408        }
409        
410        return name;
411    }
412    
413    /**
414     * Determines if a tag with given name already exists
415     * @param providers The tag providers
416     * @param name The name of the tag
417     * @param contextualParameters Contextual parameters transmitted by the environment.
418     * @return true if the tag exists
419     */
420    protected boolean _hasTag(Set<TagProvider<? extends Tag>> providers, String name, Map<String, Object> contextualParameters)
421    {
422        for (TagProvider<? extends Tag> provider : providers)
423        {
424            if (provider.hasTag(name, contextualParameters))
425            {
426                return true;
427            }
428        }
429        return false;
430    }
431}