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.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.context.Context;
026import org.apache.avalon.framework.context.ContextException;
027import org.apache.avalon.framework.context.Contextualizable;
028import org.apache.cocoon.components.ContextHelper;
029import org.apache.cocoon.environment.Request;
030
031import org.ametys.cms.tag.AbstractTagProvider;
032import org.ametys.cms.tag.Tag;
033import org.ametys.plugins.repository.AmetysObject;
034import org.ametys.plugins.repository.AmetysObjectIterable;
035import org.ametys.plugins.repository.AmetysRepositoryException;
036import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
037import org.ametys.plugins.repository.TraversableAmetysObject;
038
039/**
040 * Abstract class representing a jcr tag provider. <br>
041 * @param <T> the tag class
042 */
043public abstract class AbstractJCRTagProvider<T extends Tag> extends AbstractTagProvider<T> implements Contextualizable
044{
045    /** The request attribute name with cache information */
046    protected static final String CACHE_REQUEST_ATTRIBUTE = AbstractJCRTagProvider.class.getName() + "$cache";
047
048    /** The context */
049    protected Context _context;
050    
051    @Override
052    public void configure(Configuration configuration) throws ConfigurationException
053    {
054        super.configure(configuration);
055        _id = configuration.getAttribute("id");
056    }
057
058    @Override
059    public void contextualize(Context context) throws ContextException
060    {
061        _context = context;
062    }
063    
064    /**
065     * Get the Map of tags by their unique name
066     * @param contextualParameters The contextual parameters
067     * @return The non null cache
068     * @throws RepositoryException If the cache cannot be filled
069     */
070    protected Map<String, T> _getCache(Map<String, Object> contextualParameters) throws RepositoryException
071    {
072        Request request = ContextHelper.getRequest(_context);
073        if (request == null)
074        {
075            return new HashMap<>();
076        }
077        
078        @SuppressWarnings("unchecked")
079        Map<String, T> cache = (Map<String, T>) request.getAttribute(CACHE_REQUEST_ATTRIBUTE + "$" + getId());
080        if (cache == null)
081        {
082            cache = new HashMap<>();
083            request.setAttribute(CACHE_REQUEST_ATTRIBUTE + "$" + getId(), cache);
084            
085            TraversableAmetysObject rootNode = getRootNode(contextualParameters);
086            _fillCache(rootNode, null, cache);
087        }
088        
089        return cache;
090    }
091    
092    /**
093     * Clear the request cache
094     */
095    public void clearCache()
096    {
097        Request request = ContextHelper.getRequest(_context);
098        if (request != null)
099        {
100            request.removeAttribute(CACHE_REQUEST_ATTRIBUTE + "$" + getId());
101        }
102    }
103    
104    @Override
105    public Map<String, T> getTags(Map<String, Object> contextualParameters)
106    {
107        Map<String, T> tags = new HashMap<>();
108        
109        try
110        {
111            Map<String, T> cache = _getCache(contextualParameters);
112            
113            TraversableAmetysObject rootNode = getRootNode(contextualParameters);
114            
115            try (AmetysObjectIterable<AmetysObject> it = rootNode.getChildren())
116            {
117                for (AmetysObject object : it)
118                {
119                    if (object instanceof JCRTag)
120                    {
121                        T tag = cache.get(object.getName());
122                        tags.put(object.getId(), tag);
123                    }
124                }
125            }
126        }
127        catch (RepositoryException e)
128        {
129            getLogger().error("Unable to get JCR tags", e);
130        }
131        
132        return tags;
133    }
134    
135    @Override
136    public T getTag(String tagName, Map<String, Object> contextualParameters)
137    {
138        try
139        {
140            Map<String, T> cache = _getCache(contextualParameters);
141            return cache.get(tagName);
142        }
143        catch (RepositoryException e) 
144        {
145            getLogger().error("Unable to get JCR tags", e);
146            return null;
147        }
148    }
149    
150    @Override
151    public boolean hasTag(String tagName, Map<String, Object> contextualParameters)
152    {
153        try
154        {
155            Map<String, T> cache = _getCache(contextualParameters);
156            return cache.containsKey(tagName);
157        }
158        catch (RepositoryException e) 
159        {
160            getLogger().error("Unable to get JCR tags", e);
161            return false;
162        }
163    }
164    
165    /**
166     * Fill cache 
167     * @param parentTagNode The parent tag node
168     * @param parentTag The parent tag
169     * @param cache The cache
170     * @throws RepositoryException If an error occurred
171     */
172    protected abstract void _fillCache(TraversableAmetysObject parentTagNode, T parentTag, Map<String, T> cache) throws RepositoryException;
173    
174    
175    /**
176     * Get the root node for tags
177     * @param contextualParameters The contextual parameters
178     * @return The root node
179     * @throws RepositoryException if an error occurred
180     */
181    public abstract ModifiableTraversableAmetysObject getRootNode (Map<String, Object> contextualParameters) throws RepositoryException;
182    
183    /**
184     * Get a node or create it if not exist.
185     * @param parentNode the parent node
186     * @param nodeName the node name
187     * @param nodeType the node type
188     * @return the node
189     * @throws AmetysRepositoryException if an error occurred
190     */
191    protected ModifiableTraversableAmetysObject _getOrCreateNode(ModifiableTraversableAmetysObject parentNode, String nodeName, String nodeType) throws AmetysRepositoryException
192    {
193        ModifiableTraversableAmetysObject definitionsNode;
194        if (parentNode.hasChild(nodeName))
195        {
196            definitionsNode = parentNode.getChild(nodeName);
197        }
198        else
199        {
200            definitionsNode = parentNode.createChild(nodeName, nodeType);
201            parentNode.saveChanges();
202        }
203        return definitionsNode;
204    }
205    
206}