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;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import javax.jcr.Node;
023import javax.jcr.NodeIterator;
024import javax.jcr.RepositoryException;
025import javax.jcr.Session;
026
027/**
028 * Implementation of {@link AmetysObjectIterable} based on a underlying JCR {@link NodeIterator}.<br><br>
029 * <b>Please note that this implementation only works with NodeIterators containing nodes actually bound to AmetysObjects.<br>
030 * If a node exists in the iterator and does not correspond to an {@link AmetysObject}, an {@link AmetysRepositoryException} will be thrown.</b><br>
031 * Unless {@link AmetysObjectIterator#skip(long)} is called on the underlying Iterator, 
032 * results are buffered so that one may safely call {@link #iterator()} more than once, 
033 * even if the underlying {@link NodeIterator} will be consumed only once.<br>
034 * If {@link AmetysObjectIterator#skip(long)} is called, any subsequent call to {@link #iterator()} will throw an {@link IllegalStateException}. 
035 * @param <A> the actual type of {@link AmetysObject}s.
036 */
037public class NodeIteratorIterable<A extends AmetysObject> implements AmetysObjectIterable<A>
038{
039    AmetysObjectResolver _resolver;
040    String _parentPath;
041    Session _session;
042    List<A> _buffer = new ArrayList<>();
043    boolean _skipCalled;
044    private NodeIterator _iterator;
045    
046    
047    /**
048     * Creates a {@link NodeIteratorIterable}.
049     * @param resolver the application {@link AmetysObjectResolver}.
050     * @param iterator the underlying {@link NodeIterator}.
051     * @param parentPath the parent path in the Ametys hierarchy of all
052     *                   {@link AmetysObject} being returned.
053     * @param session the JCR Session corresponding to the specified NodeIterator
054     */
055    public NodeIteratorIterable(AmetysObjectResolver resolver, NodeIterator iterator, String parentPath, Session session)
056    {
057        _resolver = resolver;
058        _iterator = iterator;
059        _parentPath = parentPath;
060        _session  = session;
061    }
062    
063    public long getSize()
064    {
065        return _iterator.getSize();
066    }
067    
068    public AmetysObjectIterator<A> iterator()
069    {
070        if (_skipCalled)
071        {
072            throw new IllegalStateException("iterator() cannot be called on NodeIteratorIterable after skip() have been called. Results would be inconsistent.");
073        }
074        
075        return new NodeIteratorIterator(_iterator);
076    }
077    
078    public void close()
079    {
080        if (_session != null && _session.isLive())
081        {
082            _session.logout();
083        }
084    }
085    
086    class NodeIteratorIterator implements AmetysObjectIterator<A>
087    {
088        private NodeIterator _it;
089        private int _position;
090        
091        public NodeIteratorIterator(NodeIterator it)
092        {
093            _it = it;
094        }
095        
096        public long getPosition()
097        {
098            return _position;
099        }
100        
101        public long getSize()
102        {
103            return _it.getSize();
104        }
105        
106        public void skip(long skipNum)
107        {
108            if (skipNum > _buffer.size() - _position)
109            {
110                _skipCalled = true;
111                _it.skip(skipNum - (_buffer.size() - _position));
112                
113                for (int i = _buffer.size() - _position; i < skipNum; i++)
114                {
115                    _buffer.add(null);
116                }
117            }
118            
119            _position += skipNum;
120        }
121        
122        public boolean hasNext()
123        {
124            return _position < _buffer.size() || _it.hasNext();
125        }
126        
127        public A next()
128        {
129            if  (_position < _buffer.size())
130            {
131                return _buffer.get(_position++);
132            }
133            
134            Node node = _it.nextNode();
135            _position++;
136            
137            A result = null;
138            try
139            {
140                result = _resolver.resolve(_parentPath, node, null, false);
141                return result;
142            }
143            catch (RepositoryException e)
144            {
145                throw new AmetysRepositoryException("An error occured while resolving", e);
146            }
147            finally
148            {
149                // add something to the buffer, even if its null, 
150                // so that the buffer size matches the current position
151                _buffer.add(result);
152            }
153        }
154        
155        public void remove()
156        {
157            _it.remove();
158        }
159    }
160}