001/*
002 *  Copyright 2010 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 */
016
017package org.ametys.plugins.repository.collection;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.List;
023
024import javax.jcr.Node;
025import javax.jcr.NodeIterator;
026import javax.jcr.RepositoryException;
027import javax.jcr.Session;
028
029import org.apache.avalon.framework.configuration.Configuration;
030import org.apache.avalon.framework.configuration.ConfigurationException;
031
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysObjectIterable;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.ChainedAmetysObjectIterable;
036import org.ametys.plugins.repository.NodeIteratorIterable;
037import org.ametys.plugins.repository.jcr.SimpleAmetysObjectFactory;
038
039/**
040 * Factory for {@link AmetysObjectCollection}.
041 */
042public class AmetysObjectCollectionFactory extends SimpleAmetysObjectFactory
043{
044    /** JCR nodetype for the collection itself */
045    public static final String COLLECTION_NODETYPE = "ametys:collection";
046    
047    /** JCR nodetype for collection elements (ie. nodes "between" the collection and contained contents) */
048    public static final String COLLECTION_ELEMENT_NODETYPE = "ametys:collectionElement";
049    
050    @Override
051    public void configure(Configuration configuration) throws ConfigurationException
052    {
053        // does nothing, scheme and nodetypes are not obtained through configuration
054    }
055
056    @Override
057    public String getScheme()
058    {
059        return "collection";
060    }
061    
062    @Override
063    public Collection<String> getNodetypes()
064    {
065        ArrayList<String> nodetypes = new ArrayList<>();
066        
067        nodetypes.add(COLLECTION_NODETYPE);
068        nodetypes.add(COLLECTION_ELEMENT_NODETYPE);
069        
070        return Collections.unmodifiableCollection(nodetypes);
071    }
072    
073    @Override
074    @SuppressWarnings("unchecked")
075    public AmetysObjectCollection getAmetysObject(Node node, String parentPath) throws AmetysRepositoryException, RepositoryException
076    {
077        String nodeType = node.getPrimaryNodeType().getName();
078        
079        if (nodeType.equals(COLLECTION_NODETYPE))
080        {
081            // c'est directement une collection
082            return new AmetysObjectCollection(node, parentPath, this);
083        }
084        
085        // sinon, c'est un élément de collection, on remonte jusqu'à la collection
086        Node contextNode = node;
087        while (!COLLECTION_NODETYPE.equals(contextNode.getPrimaryNodeType().getName()))
088        {
089            contextNode = contextNode.getParent();
090        }
091        
092        return new AmetysObjectCollection(contextNode, parentPath, this);
093    }
094    
095    /**
096     * Returns the parent of the given {@link AmetysObjectCollection}
097     * @param object an {@link AmetysObjectCollection}
098     * @return the parent of the given {@link AmetysObjectCollection}
099     * @throws AmetysRepositoryException if an error occurs
100     */
101    public AmetysObject getParent(AmetysObjectCollection object) throws AmetysRepositoryException
102    {
103        Node node = object.getNode();
104        try
105        {
106            Node parentNode = node.getParent();
107            return _resolver.resolve(parentNode, false);
108        }
109        catch (RepositoryException ex)
110        {
111            throw new AmetysRepositoryException("An error occured during resolving parent object of object " + object.getName(), ex);
112        }
113    }
114
115    /**
116     * Returns a single {@link AmetysObject} given its path and JCR Node.<br>
117     * This method should never been called by clients.
118     * @param <A> the type of the composite {@link AmetysObject}.
119     * @param parentPath the path of the collection
120     * @param node the node of the child
121     * @param subPath the subpath in the Ametys hierarchy
122     * @return an {@link AmetysObject}
123     * @throws AmetysRepositoryException if an error occurs.
124     */
125    @SuppressWarnings("unchecked")
126    public <A extends AmetysObject> A getObject(String parentPath, Node node, String subPath) throws AmetysRepositoryException
127    {
128        try
129        {
130            return (A) _resolver.resolve(parentPath, node, subPath, false);
131        }
132        catch (RepositoryException e)
133        {
134            throw new AmetysRepositoryException("An error occured while resolving Node", e);
135        }
136    }
137    
138    /**
139     * Creates a child object in the collection and resolve it to an {@link AmetysObject}.
140     * @param <A> the type of the composite {@link AmetysObject}.
141     * @param parentPath the parentPath of the new object.
142     * @param parentNode the parent JCR Node of the new object, corresponding to a collection element.
143     * @param name the name of the new object.
144     * @param type the type of the Node backing the new object.
145     * @return the newly created {@link AmetysObject}.
146     * @throws AmetysRepositoryException if an error occurs.
147     */
148    @SuppressWarnings("unchecked")
149    public <A extends AmetysObject> A createChild(String parentPath, Node parentNode, String name, String type) throws AmetysRepositoryException
150    {
151        try
152        {
153            return (A) _resolver.createAndResolve(parentPath, parentNode, name, type);
154        }
155        catch (RepositoryException e)
156        {
157            throw new AmetysRepositoryException("An error occured while creating Node", e);
158        }
159    }
160    
161    /**
162     * Returns the {@link AmetysObject}s children of the JCR Node backing an {@link AmetysObjectCollection}.<br>
163     * This method should never been called by clients.
164     * @param parentPath the parent path in the Ametys hierarchy of all {@link AmetysObject} being returned.
165     * @param collectionNode the JCR Node backing the {@link AmetysObjectCollection}.
166     * @return the {@link AmetysObject}s children.
167     */
168    @SuppressWarnings("unchecked")
169    public AmetysObjectIterable getChildren(String parentPath, Node collectionNode)
170    {
171        List<AmetysObjectIterable> iterators = new ArrayList<>();
172            
173        try
174        {
175            NodeIterator it = collectionNode.getNodes();
176            _addFirstLevelChildren(parentPath, iterators, it, collectionNode.getSession());
177        }
178        catch (RepositoryException ex)
179        {
180            throw new AmetysRepositoryException("An error occured while iterating inside the collection", ex);
181        }
182        
183        return new ChainedAmetysObjectIterable(iterators);
184    }
185    
186    private void _addFirstLevelChildren(String parentPath, List<AmetysObjectIterable> iterators, NodeIterator it, Session session) throws RepositoryException
187    {
188        // the collection itself could have some other child nodes which should be ignored here
189        while (it.hasNext())
190        {
191            Node nextChild = it.nextNode();
192            if (nextChild.getPrimaryNodeType().getName().equals(AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE))
193            {
194                _addNextLevelChildren(parentPath, iterators, nextChild.getNodes(), session);
195            }
196        }
197    }
198    
199    private void _addNextLevelChildren(String parentPath, List<AmetysObjectIterable> iterators, NodeIterator it, Session session) throws RepositoryException
200    {
201        if (it.hasNext())
202        {
203            Node firstNode = it.nextNode();
204            if (firstNode.getPrimaryNodeType().getName().equals(AmetysObjectCollectionFactory.COLLECTION_ELEMENT_NODETYPE))
205            {
206                NodeIterator firstChildIt = firstNode.getNodes();
207                _addNextLevelChildren(parentPath, iterators, firstChildIt, session);
208                
209                while (it.hasNext())
210                {
211                    NodeIterator childIt = it.nextNode().getNodes();
212                    _addNextLevelChildren(parentPath, iterators, childIt, session);
213                }
214            }
215            else
216            {
217                iterators.add(new NodeIteratorIterable(_resolver, new WrapperNodeIterator(firstNode, it), parentPath, session));
218            }
219        }
220    }
221    
222    private class WrapperNodeIterator implements NodeIterator
223    {
224        private Node _firstElement;
225        private NodeIterator _it;
226        
227        private boolean _firstElementUsed;
228        
229        public WrapperNodeIterator(Node firstElement, NodeIterator it)
230        {
231            _firstElement = firstElement;
232            _it = it;
233            _firstElementUsed = false;
234        }
235        
236        public Node nextNode()
237        {
238            if (!_firstElementUsed)
239            {
240                _firstElementUsed = true;
241                return _firstElement;
242            }
243            
244            return _it.nextNode();
245        }
246
247        public long getPosition()
248        {
249            if (!_firstElementUsed)
250            {
251                return 0;
252            }
253            
254            return _it.getPosition();
255        }
256
257        public long getSize()
258        {
259            return _it.getSize();
260        }
261
262        public void skip(long skipNum)
263        {
264            if (skipNum < 0)
265            {
266                throw new IllegalArgumentException("skipNum must not be negative");
267            }
268
269            if (skipNum == 0)
270            {
271                return;
272            }
273            
274            if (!_firstElementUsed)
275            {
276                _firstElementUsed = true;
277                if (skipNum > 1)
278                {
279                    _it.skip(skipNum - 1);
280                }
281            }
282            else
283            {
284                _it.skip(skipNum);
285            }
286        }
287
288        public boolean hasNext()
289        {
290            if (!_firstElementUsed)
291            {
292                return true;
293            }
294            
295            return _it.hasNext();
296        }
297
298        public Object next()
299        {
300            return nextNode();
301        }
302
303        public void remove()
304        {
305            throw new UnsupportedOperationException("remove is unsupported");
306        }
307    }
308}