001/*
002 *  Copyright 2014 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.HashMap;
019import java.util.Map;
020
021import javax.jcr.RepositoryException;
022
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.components.ContextHelper;
028import org.apache.cocoon.environment.Request;
029
030import org.ametys.cms.tag.StaticTagProvider;
031import org.ametys.cms.tag.Tag;
032import org.ametys.cms.tag.TagTargetType;
033import org.ametys.cms.tag.TagTargetTypeExtensionPoint;
034import org.ametys.plugins.repository.AmetysObject;
035import org.ametys.plugins.repository.AmetysObjectIterable;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.AmetysRepositoryException;
038import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
039import org.ametys.plugins.repository.RepositoryConstants;
040import org.ametys.plugins.repository.TraversableAmetysObject;
041import org.ametys.runtime.i18n.I18nizableText;
042
043/**
044 * Class representing a jcr tag provider. <br>
045 */
046public class JCRTagProvider extends StaticTagProvider
047{
048    /** Constant for plugin node name */ 
049    public static final String PLUGIN_NODE_NAME = "cms";
050    /** JCR nodetype for tags */
051    public static final String TAGS_NODETYPE = RepositoryConstants.NAMESPACE_PREFIX + ":tags";
052    /** JCR node name for tags */
053    public static final String TAGS_NODENAME = "tags";
054    
055    /** The request attribute name with cache information */
056    protected static final String CACHE_REQUEST_ATTRIBUTE = JCRTagProvider.class.getName() + "$cache";
057    
058    /** The Ametys object resolver */
059    protected AmetysObjectResolver _resolver;
060    
061    @Override
062    public void configure(Configuration configuration) throws ConfigurationException
063    {
064        _id = configuration.getAttribute("id");
065        _label = configureLabel(configuration, "plugin." + _pluginName);
066        _description = configureDescription(configuration, "plugin." + _pluginName);
067    }
068    
069    @Override
070    public void service(ServiceManager smanager) throws ServiceException
071    {
072        super.service(smanager);
073        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
074        _targetTypeEP = (TagTargetTypeExtensionPoint) smanager.lookup(TagTargetTypeExtensionPoint.ROLE);
075    }
076    
077    
078    /**
079     * Get the Map of tags by their unique name
080     * @param contextualParameters The contextual parameters
081     * @return The non null cache
082     * @throws RepositoryException If the cache cannot be filled
083     */
084    protected Map<String, Object> _getCache(Map<String, Object> contextualParameters) throws RepositoryException
085    {
086        Request request = ContextHelper.getRequest(_context);
087        if (request == null)
088        {
089            return new HashMap<>();
090        }
091        
092        @SuppressWarnings("unchecked")
093        Map<String, Object> cache = (Map<String, Object>) request.getAttribute(CACHE_REQUEST_ATTRIBUTE);
094        if (cache == null)
095        {
096            cache = new HashMap<>();
097            request.setAttribute(CACHE_REQUEST_ATTRIBUTE, cache);
098            
099            TraversableAmetysObject rootNode = getRootNode(contextualParameters);
100            _fillCache(rootNode, null, cache);
101        }
102        
103        return cache;
104    }
105    
106    /**
107     * Fill cache 
108     * @param parentTagNode The parent tag node
109     * @param parentTag The parent tag
110     * @param cache The cache
111     * @throws RepositoryException If an error occurred
112     */
113    protected void _fillCache(TraversableAmetysObject parentTagNode, Tag parentTag, Map<String, Object> cache) throws RepositoryException
114    {
115        for (AmetysObject child : parentTagNode.getChildren())
116        {
117            if (child instanceof JCRTag)
118            {
119                JCRTag jcrTag = (JCRTag) child;
120                TagTargetType targeType = _targetTypeEP.getTagTargetType(jcrTag.getTargetType());
121                Tag tag = new Tag(jcrTag.getId(), jcrTag.getName(), parentTag, new I18nizableText(jcrTag.getTitle()), new I18nizableText(jcrTag.getDescription()), jcrTag.getVisibility(), targeType);
122                cache.put(child.getName(), tag);
123                
124                if (parentTag != null)
125                {
126                    parentTag.addTag(tag);
127                }
128                
129                _fillCache(jcrTag, tag, cache);
130            }
131        }
132    }
133    
134    @Override
135    public Map<String, Tag> getTags(Map<String, Object> contextualParameters)
136    {
137        Map<String, Tag> tags = new HashMap<>();
138        
139        try
140        {
141            Map<String, Object> cache = _getCache(contextualParameters);
142            
143            TraversableAmetysObject rootNode = getRootNode(contextualParameters);
144            
145            try (AmetysObjectIterable<AmetysObject> it = rootNode.getChildren())
146            {
147                for (AmetysObject object : it)
148                {
149                    if (object instanceof JCRTag)
150                    {
151                        tags.put(object.getId(), (Tag) cache.get(object.getName()));
152                    }
153                }
154            }
155        }
156        catch (RepositoryException e)
157        {
158            getLogger().error("Unable to get JCR tags", e);
159        }
160        
161        return tags;
162    }
163    
164    @Override
165    public boolean hasTag(String tagName, Map<String, Object> contextualParameters)
166    {
167        try
168        {
169            Map<String, Object> cache = _getCache(contextualParameters);
170            if (cache.containsKey(tagName))
171            {
172                return cache.get(tagName) instanceof Tag;
173            }
174            else
175            {
176                return false;
177            }
178        }
179        catch (RepositoryException e) 
180        {
181            getLogger().error("Unable to get JCR tags", e);
182            return false;
183        }
184    }
185    
186    @Override
187    public Tag getTag(String tagName, Map<String, Object> contextualParameters)
188    {
189        try
190        {
191            Map<String, Object> cache = _getCache(contextualParameters);
192            Object o = cache.get(tagName);
193            if (o instanceof Tag)
194            {
195                return  (Tag) o;
196            }
197            else
198            {
199                return null;
200            }
201        }
202        catch (RepositoryException e) 
203        {
204            getLogger().error("Unable to get JCR tags", e);
205            return null;
206        }
207    }
208    
209    /**
210     * Get the root node for tags
211     * @param contextualParameters The contextual parameters
212     * @return The root node
213     * @throws RepositoryException if an error occurred
214     */
215    public ModifiableTraversableAmetysObject getRootNode (Map<String, Object> contextualParameters) throws RepositoryException
216    {
217        try
218        {
219            ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins");
220            
221            ModifiableTraversableAmetysObject pluginNode = _getOrCreateNode(pluginsNode, PLUGIN_NODE_NAME, "ametys:unstructured");
222            
223            return _getOrCreateNode(pluginNode, TAGS_NODENAME, TAGS_NODETYPE);
224        }
225        catch (AmetysRepositoryException e)
226        {
227            throw new AmetysRepositoryException("Unable to get the JCR tags root node", e);
228        }
229    }
230    
231    
232    /**
233     * Get a node or create it if not exist.
234     * @param parentNode the parent node
235     * @param nodeName the node name
236     * @param nodeType the node type
237     * @return the node
238     * @throws AmetysRepositoryException if an error occurred
239     */
240    protected ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException
241    {
242        ModifiableTraversableAmetysObject definitionsNode;
243        if (parentNode.hasChild(nodeName))
244        {
245            definitionsNode = parentNode.getChild(nodeName);
246        }
247        else
248        {
249            definitionsNode = parentNode.createChild(nodeName, nodeType);
250            parentNode.saveChanges();
251        }
252        return definitionsNode;
253    }
254    
255    /**
256     * Creates the XPath query corresponding to specified tag name
257     * @param tagName the tag name
258     * @return the created XPath query
259     */
260    public static String getXPathQuery(String tagName)
261    {
262        return "//element(" + tagName + "," + TagFactory.TAG_NODETYPE + ")";
263    }
264    
265}