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