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}