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 */ 016package org.ametys.workspaces.repository.jcr; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.List; 024import java.util.Map; 025import java.util.TreeMap; 026 027import javax.jcr.Node; 028import javax.jcr.NodeIterator; 029import javax.jcr.Property; 030import javax.jcr.PropertyIterator; 031import javax.jcr.PropertyType; 032import javax.jcr.RepositoryException; 033import javax.jcr.Session; 034import javax.jcr.Value; 035 036import org.apache.cocoon.ProcessingException; 037import org.apache.cocoon.environment.ObjectModelHelper; 038import org.apache.cocoon.environment.Request; 039import org.apache.cocoon.xml.AttributesImpl; 040import org.apache.cocoon.xml.XMLUtils; 041import org.apache.commons.lang.StringUtils; 042import org.xml.sax.Attributes; 043import org.xml.sax.SAXException; 044 045import org.ametys.core.util.DateUtils; 046import org.ametys.runtime.config.Config; 047 048/** 049 * Generate the content of a node: 050 * <ul> 051 * <li>properties 052 * <li>children 053 * <li>referers 054 * </ul> 055 */ 056public class RepositoryNodeGenerator extends AbstractRepositoryGenerator 057{ 058 private static final Comparator<Node> __ALPHA_NODE_COMPARATOR = new NodeNameComparator(); 059 060 private static final Comparator<Node> __REVERSE_ALPHA_NODE_COMPARATOR = new NodeNameComparator(false); 061 062 public void generate() throws IOException, SAXException, ProcessingException 063 { 064 Request request = ObjectModelHelper.getRequest(objectModel); 065 066 String workspaceName = parameters.getParameter("workspace", "default"); 067 // Can be "document", "alpha" or "reverseAlpha". 068 String defaultOrder = Config.getInstance() != null ? Config.getInstance().getValue("repository.default.sort") : "document"; 069 String order = parameters.getParameter("order", defaultOrder); 070 if (StringUtils.isBlank(order)) 071 { 072 order = defaultOrder; 073 } 074 075 _getRepository(request, workspaceName); 076 077 if (getLogger().isDebugEnabled()) 078 { 079 getLogger().debug("Trying to generate content for a node"); 080 } 081 082 contentHandler.startDocument(); 083 084 085 if (workspaceName != null) 086 { 087 AttributesImpl repositoryAttributes = new AttributesImpl(); 088 repositoryAttributes.addCDATAAttribute("workspace", workspaceName); 089 XMLUtils.startElement(contentHandler, "repository", repositoryAttributes); 090 } 091 else 092 { 093 XMLUtils.startElement(contentHandler, "repository"); 094 } 095 096 try 097 { 098 _saxNode(_session, order); 099 } 100 catch (RepositoryException e) 101 { 102 throw new ProcessingException("Unable to retrieve node for path: '" + source + "'", e); 103 } 104 105 XMLUtils.endElement(contentHandler, "repository"); 106 107 contentHandler.endDocument(); 108 } 109 110 private void _saxNode(Session session, String order) throws SAXException, RepositoryException 111 { 112 Node node = null; 113 Node rootNode = session.getRootNode(); 114 String path = parameters.getParameter("path", ""); 115 long start = parameters.getParameterAsLong("start", -1); 116 long end = parameters.getParameterAsLong("end", -1); 117 118 if (StringUtils.isEmpty(path)) 119 { 120 node = rootNode; 121 } 122 else 123 { 124 node = rootNode.getNode(path); 125 } 126 127 AttributesImpl nodeAttributes = new AttributesImpl(); 128 129 if (rootNode != node) 130 { 131 nodeAttributes.addCDATAAttribute("parentPath", node.getParent().getPath()); 132 } 133 else 134 { 135 // Parent path is empty if this is the JCR root node 136 nodeAttributes.addCDATAAttribute("parentPath", ""); 137 } 138 139 if (start > -1 && end > -1) 140 { 141 nodeAttributes.addCDATAAttribute("start", Long.toString(start)); 142 nodeAttributes.addCDATAAttribute("end", Long.toString(end)); 143 } 144 145 boolean hasOrderableChildNodes = node.getPrimaryNodeType().hasOrderableChildNodes(); 146 147 nodeAttributes.addCDATAAttribute("name", node.getName()); 148 nodeAttributes.addCDATAAttribute("path", node.getPath()); 149 nodeAttributes.addCDATAAttribute("pathWithGroups", NodeGroupHelper.getPathWithGroups(node)); 150 nodeAttributes.addCDATAAttribute("index", String.valueOf(node.getIndex())); 151 nodeAttributes.addCDATAAttribute("hasOrderableChildNodes", Boolean.toString(hasOrderableChildNodes)); 152 nodeAttributes.addCDATAAttribute("isNew", Boolean.toString(node.isNew())); 153 nodeAttributes.addCDATAAttribute("isModified", Boolean.toString(node.isModified())); 154 nodeAttributes.addCDATAAttribute("noChild", Boolean.toString(!node.hasNodes())); 155 nodeAttributes.addCDATAAttribute("type", "node"); 156 157// String realOrder = hasOrderableChildNodes ? "document" : order; 158 159 XMLUtils.startElement(contentHandler, "node", nodeAttributes); 160 161 // Properties 162 _saxProperties(node); 163 164 // Children 165 _saxChildren(node, start, end, order); 166 167 // Referers 168 _saxReferers(node); 169 170 XMLUtils.endElement(contentHandler, "node"); 171 } 172 173 private void _saxProperties(Node node) throws SAXException, RepositoryException 174 { 175 Map<String, Property> jcrProperties = new TreeMap<>(); 176 Map<String, Property> otherProperties = new TreeMap<>(); 177 178 PropertyIterator itProperty = node.getProperties(); 179 while (itProperty.hasNext()) 180 { 181 Property property = itProperty.nextProperty(); 182 String propertyName = property.getName(); 183 184 if (propertyName.startsWith("jcr:")) 185 { 186 jcrProperties.put(propertyName, property); 187 } 188 else 189 { 190 otherProperties.put(propertyName, property); 191 } 192 } 193 194 for (Property property : jcrProperties.values()) 195 { 196 _saxProperty(property, node); 197 } 198 199 for (Property property : otherProperties.values()) 200 { 201 _saxProperty(property, node); 202 } 203 204 XMLUtils.createElement(contentHandler, "isLocked", String.valueOf(node.isLocked())); 205 XMLUtils.createElement(contentHandler, "isCheckedOut", String.valueOf(node.isCheckedOut())); 206 XMLUtils.createElement(contentHandler, "isNew", Boolean.toString(node.isNew())); 207 } 208 209 private void _saxProperty(Property property, Node parentNode) throws SAXException, RepositoryException 210 { 211 boolean isMultiple = property.getDefinition().isMultiple(); 212 AttributesImpl propertyAttributes = new AttributesImpl(); 213 boolean editable = !property.getDefinition().isProtected() && !parentNode.isLocked(); 214 215 propertyAttributes.addCDATAAttribute("name", property.getName()); 216 propertyAttributes.addCDATAAttribute("multiple", String.valueOf(isMultiple)); 217 propertyAttributes.addCDATAAttribute("type", PropertyType.nameFromValue(property.getType())); 218 propertyAttributes.addCDATAAttribute("editable", String.valueOf(editable)); 219 propertyAttributes.addCDATAAttribute("new", Boolean.toString(property.isNew())); 220 propertyAttributes.addCDATAAttribute("modified", Boolean.toString(property.isModified())); 221 222 XMLUtils.startElement(contentHandler, "property", propertyAttributes); 223 224 if (isMultiple) 225 { 226 Value[] values = property.getValues(); 227 228 for (int i = 0; i < values.length; i++) 229 { 230 _saxValue(values[i]); 231 } 232 } 233 else 234 { 235 _saxValue(property.getValue()); 236 } 237 238 XMLUtils.endElement(contentHandler, "property"); 239 } 240 241 private void _saxChildren(Node node, long start, long end, String order) throws SAXException, RepositoryException 242 { 243 NodeIterator itNode = node.getNodes(); 244 245 long startIndex = Math.max(0, start); 246 long endIndex = end >= 0 ? end : (itNode.getSize() - 1); 247 248 long childCount = endIndex - startIndex + 1; 249 250 if (childCount > NodeGroupHelper.MAX_CHILDREN_PER_NODE) 251 { 252 _saxGroups(node, startIndex, childCount); 253 } 254 else 255 { 256 if ("alpha".equals(order) || "reverseAlpha".equals(order)) 257 { 258 Collection<Node> children = _extractNodes(itNode, startIndex, endIndex, "alpha".equals(order)); 259 for (Node child : children) 260 { 261 _saxSubnode(child); 262 } 263 } 264 else 265 { 266 itNode.skip(startIndex); 267 for (long i = startIndex; i <= endIndex && itNode.hasNext(); i++) 268 { 269 _saxSubnode(itNode.nextNode()); 270 } 271 } 272 } 273 } 274 275 private Collection<Node> _extractNodes(NodeIterator itNodes, long start, long end, boolean ascending) 276 { 277 List<Node> nodes = new ArrayList<>((int) itNodes.getSize()); 278 279 // Fill in the list with all elements. 280 while (itNodes.hasNext()) 281 { 282 nodes.add(itNodes.nextNode()); 283 } 284 285 // Sort the collection. 286 Collections.sort(nodes, ascending ? __ALPHA_NODE_COMPARATOR : __REVERSE_ALPHA_NODE_COMPARATOR); 287 288 return nodes.subList((int) start, (int) end + 1); 289 } 290 291 /** 292 * Generate node groups. 293 * @param node The node to consider 294 * @param startIndex The index to start the group 295 * @param childCount The group size 296 * @throws RepositoryException if an error occurred 297 * @throws SAXException if an error occurred 298 */ 299 private void _saxGroups(Node node, long startIndex, long childCount) throws RepositoryException, SAXException 300 { 301 // Compute the number of levels. 302 long level = (long) Math.ceil(Math.log(childCount) / Math.log(NodeGroupHelper.MAX_CHILDREN_PER_NODE)) - 1; 303 long childrenPerGroup = (long) Math.pow(NodeGroupHelper.MAX_CHILDREN_PER_NODE, level); 304 long groupCount = (long) Math.ceil((double) childCount / (double) childrenPerGroup); 305 306 // SAX virtual nodes (groups). 307 for (long i = 0; i < groupCount; i++) 308 { 309 long groupStartIndex = startIndex + i * childrenPerGroup; 310 long groupEndIndex = startIndex + Math.min((i + 1) * childrenPerGroup - 1 , childCount - 1); 311 String name = String.format("%d...%d", groupStartIndex + 1, groupEndIndex + 1); 312 313 AttributesImpl atts = new AttributesImpl(); 314 atts.addCDATAAttribute("level", Long.toString(level)); 315 atts.addCDATAAttribute("index", Long.toString(i)); 316 atts.addCDATAAttribute("start", Long.toString(groupStartIndex)); 317 atts.addCDATAAttribute("end", Long.toString(groupEndIndex)); 318 atts.addCDATAAttribute("path", node.getPath()); 319 atts.addCDATAAttribute("name", name); 320 atts.addCDATAAttribute("type", "group"); 321 XMLUtils.createElement(contentHandler, "group", atts); 322 } 323 } 324 325 /** 326 * Sax a sub node. 327 * @param node the sub node to SAX. 328 * @throws RepositoryException if an error occurred 329 * @throws SAXException if an error occurred 330 */ 331 private void _saxSubnode(Node node) throws RepositoryException, SAXException 332 { 333 AttributesImpl subNodeAttributes = new AttributesImpl(); 334 int index = node.getIndex(); 335 String name = node.getName(); 336 337 if (index != 1) 338 { 339 name = name + "[" + index + "]"; 340 } 341 342 subNodeAttributes.addCDATAAttribute("parentPath", node.getParent().getPath()); 343 subNodeAttributes.addCDATAAttribute("name", name); 344 subNodeAttributes.addCDATAAttribute("index", String.valueOf(index)); 345 subNodeAttributes.addCDATAAttribute("hasOrderableChildNodes", Boolean.toString(node.getPrimaryNodeType().hasOrderableChildNodes())); 346 subNodeAttributes.addCDATAAttribute("isNew", Boolean.toString(node.isNew())); 347 subNodeAttributes.addCDATAAttribute("isModified", Boolean.toString(node.isModified())); 348 subNodeAttributes.addCDATAAttribute("type", "node"); 349 350 if (!node.hasNodes()) 351 { 352 subNodeAttributes.addCDATAAttribute("noChild", "true"); 353 } 354 355 XMLUtils.createElement(contentHandler, "node", subNodeAttributes); 356 } 357 358 private void _saxReferers(Node node) throws SAXException, RepositoryException 359 { 360 PropertyIterator itReference = node.getReferences(); 361 362 while (itReference.hasNext()) 363 { 364 Property property = itReference.nextProperty(); 365 AttributesImpl referenceAttributes = new AttributesImpl(); 366 367 Node parent = property.getParent(); 368 369 referenceAttributes.addCDATAAttribute("path", parent.getPath()); 370 referenceAttributes.addCDATAAttribute("pathWithGroups", NodeGroupHelper.getPathWithGroups(parent)); 371 referenceAttributes.addCDATAAttribute("name", property.getName()); 372 373 XMLUtils.createElement(contentHandler, "referer", referenceAttributes); 374 } 375 } 376 377 private void _saxValue(Value value) throws RepositoryException, SAXException 378 { 379 Attributes attrs = XMLUtils.EMPTY_ATTRIBUTES; 380 String textValue = null; 381 382 switch (value.getType()) 383 { 384 case PropertyType.BINARY: 385 textValue = ""; 386 break; 387 case PropertyType.DATE: 388 textValue = DateUtils.dateToString(value.getDate().getTime()); 389 break; 390 case PropertyType.STRING: 391 case PropertyType.PATH: 392 case PropertyType.NAME: 393 case PropertyType.LONG: 394 case PropertyType.BOOLEAN: 395 case PropertyType.REFERENCE: 396 default: 397 textValue = value.getString(); 398 break; 399 } 400 401 if (textValue != null) 402 { 403 XMLUtils.createElement(contentHandler, "value", attrs, textValue); 404 } 405 } 406 407 /** 408 * Compares two nodes on its names. 409 */ 410 protected static class NodeNameComparator implements Comparator<Node> 411 { 412 413 /** Indicates if the comparators acts in ascending order. */ 414 protected boolean _ascending; 415 416 /** 417 * Constructs a node name comparator. 418 */ 419 public NodeNameComparator() 420 { 421 this(true); 422 } 423 424 /** 425 * Constructs a node name comparator, specifying ascending or descending order. 426 * @param ascending true if we want to compare node names in ascending order, false otherwise. 427 */ 428 public NodeNameComparator(boolean ascending) 429 { 430 this._ascending = ascending; 431 } 432 433 @Override 434 public int compare(Node o1, Node o2) 435 { 436 try 437 { 438 if (_ascending) 439 { 440 return o1.getName().compareTo(o2.getName()); 441 } 442 else 443 { 444 return o2.getName().compareTo(o1.getName()); 445 } 446 } 447 catch (RepositoryException e) 448 { 449 throw new RuntimeException("Impossible to get a node name.", e); 450 } 451 } 452 453 } 454 455}