]> git.argeo.org Git - gpl/argeo-jcr.git/blob - JcrContent.java
880f29eeaebf4c30f87fd850ddd8ab84036b48c1
[gpl/argeo-jcr.git] / JcrContent.java
1 package org.argeo.cms.jcr.acr;
2
3 import java.io.Closeable;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.OutputStream;
7 import java.io.PipedInputStream;
8 import java.io.PipedOutputStream;
9 import java.time.Instant;
10 import java.util.ArrayList;
11 import java.util.Arrays;
12 import java.util.Calendar;
13 import java.util.Date;
14 import java.util.GregorianCalendar;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Objects;
20 import java.util.Optional;
21 import java.util.Set;
22 import java.util.TreeSet;
23 import java.util.UUID;
24 import java.util.concurrent.ForkJoinPool;
25
26 import javax.jcr.Binary;
27 import javax.jcr.Node;
28 import javax.jcr.NodeIterator;
29 import javax.jcr.Property;
30 import javax.jcr.PropertyIterator;
31 import javax.jcr.PropertyType;
32 import javax.jcr.RepositoryException;
33 import javax.jcr.Session;
34 import javax.jcr.Value;
35 import javax.jcr.ValueFactory;
36 import javax.jcr.nodetype.NodeType;
37 import javax.jcr.nodetype.NodeTypeManager;
38 import javax.xml.namespace.QName;
39 import javax.xml.transform.Source;
40 import javax.xml.transform.stream.StreamSource;
41
42 import org.argeo.api.acr.Content;
43 import org.argeo.api.acr.CrAttributeType;
44 import org.argeo.api.acr.DName;
45 import org.argeo.api.acr.NamespaceUtils;
46 import org.argeo.api.acr.spi.ProvidedSession;
47 import org.argeo.api.cms.CmsConstants;
48 import org.argeo.cms.acr.AbstractContent;
49 import org.argeo.cms.acr.ContentUtils;
50 import org.argeo.cms.util.AsyncPipedOutputStream;
51 import org.argeo.jcr.Jcr;
52 import org.argeo.jcr.JcrException;
53 import org.argeo.jcr.JcrUtils;
54 import org.argeo.jcr.JcrxApi;
55
56 /** A JCR {@link Node} accessed as {@link Content}. */
57 public class JcrContent extends AbstractContent {
58 private JcrContentProvider provider;
59
60 private String jcrWorkspace;
61 private String jcrPath;
62
63 private final boolean isMountBase;
64
65 /* OPTIMISATIONS */
66 /**
67 * While we want to support thread-safe access, it is very likely that only one
68 * thread and only one session will be used (typically from a single-threaded
69 * UI). We therefore cache was long as the same thread is calling.
70 */
71 private Thread lastRetrievingThread = null;
72 private Node cachedNode = null;
73 private boolean caching = true;
74
75 protected JcrContent(ProvidedSession session, JcrContentProvider provider, String jcrWorkspace, String jcrPath) {
76 super(session);
77 this.provider = provider;
78 this.jcrWorkspace = jcrWorkspace;
79 this.jcrPath = jcrPath;
80
81 this.isMountBase = ContentUtils.SLASH_STRING.equals(jcrPath);
82 }
83
84 /*
85 * READ
86 */
87
88 @Override
89 public QName getName() {
90 String name = Jcr.getName(getJcrNode());
91 if (name.equals("")) {// root
92 String mountPath = provider.getMountPath();
93 name = ContentUtils.getParentPath(mountPath)[1];
94 // name = Jcr.getWorkspaceName(getJcrNode());
95 }
96 return NamespaceUtils.parsePrefixedName(provider, name);
97 }
98
99 @SuppressWarnings("unchecked")
100 @Override
101 public <A> Optional<A> get(QName key, Class<A> clss) {
102 try {
103 Node node = getJcrNode();
104 if (DName.creationdate.equals(key))
105 key = JcrName.created.qName();
106 else if (DName.getlastmodified.equals(key))
107 key = JcrName.lastModified.qName();
108 else if (DName.getcontenttype.equals(key)) {
109 String contentType = null;
110 if (node.isNodeType(NodeType.NT_FILE)) {
111 Node content = node.getNode(Node.JCR_CONTENT);
112 if (content.isNodeType(NodeType.MIX_MIMETYPE)) {
113 contentType = content.getProperty(Property.JCR_MIMETYPE).getString();
114 if (content.hasProperty(Property.JCR_ENCODING))
115 contentType = contentType + ";encoding="
116 + content.getProperty(Property.JCR_ENCODING).getString();
117 }
118 }
119 if (contentType == null)
120 contentType = "application/octet-stream";
121 return CrAttributeType.cast(clss, contentType);
122 } else if (DName.checkedOut.equals(key)) {
123 if (!node.hasProperty(Property.JCR_IS_CHECKED_OUT))
124 return Optional.empty();
125 boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
126 if (!isCheckedOut)
127 return Optional.empty();
128 // FIXME return URI
129 return (Optional<A>) Optional.of(new Object());
130 } else if (DName.checkedIn.equals(key)) {
131 if (!node.hasProperty(Property.JCR_IS_CHECKED_OUT))
132 return Optional.empty();
133 boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
134 if (isCheckedOut)
135 return Optional.empty();
136 // FIXME return URI
137 return (Optional<A>) Optional.of(new Object());
138 }
139
140 Object value = get(node, key.toString());
141 if (value instanceof List<?> lst)
142 return Optional.of((A) lst);
143 // TODO check other collections?
144 return CrAttributeType.cast(clss, value);
145 } catch (RepositoryException e) {
146 throw new JcrException(e);
147 }
148 }
149
150 @Override
151 public Iterator<Content> iterator() {
152 try {
153 return new JcrContentIterator(getJcrNode().getNodes());
154 } catch (RepositoryException e) {
155 throw new JcrException("Cannot list children of " + getJcrNode(), e);
156 }
157 }
158
159 @Override
160 protected Iterable<QName> keys() {
161 try {
162 Node node = getJcrNode();
163 Set<QName> keys = new HashSet<>();
164 for (PropertyIterator propertyIterator = node.getProperties(); propertyIterator.hasNext();) {
165 Property property = propertyIterator.nextProperty();
166 QName name = NamespaceUtils.parsePrefixedName(provider, property.getName());
167
168 // TODO convert standard names
169 if (property.getName().equals(Property.JCR_CREATED))
170 name = DName.creationdate.qName();
171 else if (property.getName().equals(Property.JCR_LAST_MODIFIED))
172 name = DName.getlastmodified.qName();
173 else if (property.getName().equals(Property.JCR_MIMETYPE))
174 name = DName.getcontenttype.qName();
175 else if (property.getName().equals(Property.JCR_IS_CHECKED_OUT)) {
176 boolean isCheckedOut = node.getProperty(Property.JCR_IS_CHECKED_OUT).getBoolean();
177 name = isCheckedOut ? DName.checkedOut.qName() : DName.checkedIn.qName();
178 }
179
180 // TODO skip technical properties
181 keys.add(name);
182 }
183 return keys;
184 } catch (RepositoryException e) {
185 throw new JcrException("Cannot list properties of " + getJcrNode(), e);
186 }
187 }
188
189 /** Cast to a standard Java object. */
190 static Object get(Node node, String property) {
191 try {
192 if (!node.hasProperty(property))
193 return null;
194 Property p = node.getProperty(property);
195 if (p.isMultiple()) {
196 Value[] values = p.getValues();
197 List<Object> lst = new ArrayList<>();
198 for (Value value : values) {
199 lst.add(convertSingleValue(value));
200 }
201 return lst;
202 } else {
203 Value value = node.getProperty(property).getValue();
204 return convertSingleValue(value);
205 }
206 } catch (RepositoryException e) {
207 throw new JcrException("Cannot cast value from " + property + " of " + node, e);
208 }
209 }
210
211 @Override
212 public boolean isMultiple(QName key) {
213 Node node = getJcrNode();
214 String p = NamespaceUtils.toFullyQualified(key);
215 try {
216 if (node.hasProperty(p)) {
217 Property property = node.getProperty(p);
218 return property.isMultiple();
219 } else {
220 return false;
221 }
222 } catch (RepositoryException e) {
223 throw new JcrException(
224 "Cannot check multiplicity of property " + p + " of " + jcrPath + " in " + jcrWorkspace, e);
225 }
226 }
227
228 @Override
229 public String getPath() {
230 try {
231 // Note: it is important to to use the default way (recursing through parents),
232 // since the session may not have access to parent nodes
233 return Content.ROOT_PATH + jcrWorkspace + getJcrNode().getPath();
234 } catch (RepositoryException e) {
235 throw new JcrException("Cannot get depth of " + getJcrNode(), e);
236 }
237 }
238
239 @Override
240 public int getDepth() {
241 try {
242 return getJcrNode().getDepth() + 1;
243 } catch (RepositoryException e) {
244 throw new JcrException("Cannot get depth of " + getJcrNode(), e);
245 }
246 }
247
248 @Override
249 public Content getParent() {
250 if (isMountBase) {
251 String mountPath = provider.getMountPath();
252 if (mountPath == null || mountPath.equals("/"))
253 return null;
254 String[] parent = ContentUtils.getParentPath(mountPath);
255 return getSession().get(parent[0]);
256 }
257 // if (Jcr.isRoot(getJcrNode())) // root
258 // return null;
259 return new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getParentPath(getJcrNode()));
260 }
261
262 @Override
263 public int getSiblingIndex() {
264 return Jcr.getIndex(getJcrNode());
265 }
266
267 @Override
268 public String getText() {
269 return JcrxApi.getXmlValue(getJcrNode());
270 }
271
272 /*
273 * MAP OPTIMISATIONS
274 */
275 @Override
276 public boolean containsKey(Object key) {
277 return Jcr.hasProperty(getJcrNode(), key.toString());
278 }
279
280 /*
281 * WRITE
282 */
283
284 protected Node openForEdit() {
285 Node node = getProvider().openForEdit(getSession(), jcrWorkspace, jcrPath);
286 getSession().notifyModification(this);
287 return node;
288 }
289
290 @Override
291 public Content add(QName name, QName... classes) {
292 try {
293 Node node = openForEdit();
294 Node child;
295 if (classes.length > 0) {
296 classes: for (int i = 0; i < classes.length; i++) {
297 if (classes[i].equals(DName.collection.qName())) {
298 List<QName> lst = new ArrayList<>(Arrays.asList(classes));
299 lst.add(0, NtType.folder.qName());
300 lst.remove(DName.collection.qName());
301 classes = lst.toArray(new QName[lst.size()]);
302 break classes;
303 }
304 }
305 QName primaryType = classes[0];
306 child = Jcr.addNode(node, name.toString(), primaryType.toString());
307
308 for (int i = 1; i < classes.length; i++)
309 child.addMixin(classes[i].toString());
310
311 if (NtType.file.qName().equals(primaryType)) {
312 // TODO optimise when we have a proper save mechanism
313 child.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED);
314 }
315 } else {
316 child = Jcr.addNode(node, name.toString(), NodeType.NT_UNSTRUCTURED);
317 }
318 saveEditedNode(node);
319 return new JcrContent(getSession(), provider, jcrWorkspace, child.getPath());
320 } catch (RepositoryException e) {
321 throw new JcrException("Cannot add child to " + jcrPath + " in " + jcrWorkspace, e);
322 }
323 }
324
325 @Override
326 public Content add(QName name, Map<QName, Object> attrs, QName... classes) {
327 if (attrs.containsKey(DName.getcontenttype.qName())) {
328 List<QName> lst = new ArrayList<>(Arrays.asList(classes));
329 lst.add(0, NtType.file.qName());
330 classes = lst.toArray(new QName[lst.size()]);
331 }
332
333 Content child = add(name, classes);
334 child.putAll(attrs);
335 return child;
336 }
337
338 @Override
339 public void remove() {
340 Node node = openForEdit();
341 Jcr.remove(node);
342 saveEditedNode(node);
343 }
344
345 private void saveEditedNode(Node node) {
346 try {
347 node.getSession().save();
348 getJcrSession().refresh(true);
349 } catch (RepositoryException e) {
350 throw new JcrException("Cannot persist " + jcrPath + " in " + jcrWorkspace, e);
351 }
352 }
353
354 @Override
355 protected void removeAttr(QName key) {
356 Node node = openForEdit();
357 Property property = Jcr.getProperty(node, key.toString());
358 if (property != null) {
359 try {
360 property.remove();
361 } catch (RepositoryException e) {
362 throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
363 }
364 }
365 saveEditedNode(node);
366 }
367
368 @Override
369 public Object put(QName key, Object value) {
370 Objects.requireNonNull(value, "Value cannot be null");
371 Node node = openForEdit();
372 try {
373 if (DName.checkedIn.equals(key) || DName.checkedOut.equals(key))
374 throw new IllegalArgumentException(
375 key + " cannot be set, use the openForEdit/freeze methods of the related content provider.");
376
377 Object old = null;
378 String property;
379 if (DName.creationdate.equals(key))
380 property = Property.JCR_CREATED;
381 else if (DName.getlastmodified.equals(key))
382 property = Property.JCR_LAST_MODIFIED;
383 else if (DName.getcontenttype.equals(key)) {
384 if (!node.isNodeType(NodeType.NT_FILE))
385 throw new IllegalStateException(DName.getcontenttype + " can only be set on a file");
386 Node content = node.getNode(Node.JCR_CONTENT);
387 old = Jcr.get(content, Property.JCR_MIMETYPE);
388 if (old != null && Jcr.hasProperty(content, Property.JCR_ENCODING))
389 old = old + ";encoding=" + Jcr.get(content, Property.JCR_ENCODING);
390 String[] str = value.toString().split(";");
391 String mimeType = str[0].trim();
392 String encoding = null;
393 if (str.length > 1) {
394 value = str[0].trim();
395 String[] eq = str[1].split("=");
396 assert eq.length == 2;
397 if ("encoding".equals(eq[0].trim()))
398 encoding = eq[1];
399 }
400 content.setProperty(Property.JCR_MIMETYPE, mimeType);
401 if (encoding != null)
402 content.setProperty(Property.JCR_ENCODING, encoding);
403 property = null;
404 } else
405 property = NamespaceUtils.toFullyQualified(key);
406
407 if (property != null) {
408 if (node.hasProperty(property)) {
409 old = convertSingleValue(node.getProperty(property).getValue());
410 }
411 Value newValue = convertSingleObject(node.getSession().getValueFactory(), value);
412 node.setProperty(property, newValue);
413 }
414 // FIXME proper edition
415 saveEditedNode(node);
416 return old;
417 } catch (RepositoryException e) {
418 throw new JcrException("Cannot set property " + key + " on " + jcrPath + " in " + jcrWorkspace, e);
419 }
420 }
421
422 @Override
423 public void addContentClasses(QName... contentClass) throws IllegalArgumentException, JcrException {
424 try {
425 Node node = openForEdit();
426 NodeTypeManager ntm = node.getSession().getWorkspace().getNodeTypeManager();
427 List<NodeType> nodeTypes = new ArrayList<>();
428 for (QName clss : contentClass) {
429 NodeType nodeType = ntm.getNodeType(NamespaceUtils.toFullyQualified(clss));
430 if (!nodeType.isMixin())
431 throw new IllegalArgumentException(clss + " is not a mixin");
432 nodeTypes.add(nodeType);
433 }
434 for (NodeType nodeType : nodeTypes) {
435 node.addMixin(nodeType.getName());
436 }
437 // FIXME proper edition
438 saveEditedNode(node);
439 } catch (RepositoryException e) {
440 throw new JcrException(
441 "Cannot add content classes " + contentClass + " to " + jcrPath + " in " + jcrWorkspace, e);
442 }
443 }
444
445 /*
446 * ACCESS
447 */
448 protected boolean exists() {
449 try {
450 return getJcrSession().itemExists(jcrPath);
451 } catch (RepositoryException e) {
452 throw new JcrException("Cannot check whether " + jcrPath + " exists", e);
453 }
454 }
455
456 @Override
457 public boolean isParentAccessible() {
458 String jcrParentPath = ContentUtils.getParentPath(jcrPath)[0];
459 if ("".equals(jcrParentPath)) // JCR root node
460 jcrParentPath = ContentUtils.SLASH_STRING;
461 try {
462 return getJcrSession().hasPermission(jcrParentPath, Session.ACTION_READ);
463 } catch (RepositoryException e) {
464 throw new JcrException("Cannot check whether parent " + jcrParentPath + " is accessible", e);
465 }
466 }
467
468 /*
469 * ADAPTERS
470 */
471 @SuppressWarnings("unchecked")
472 public <A> A adapt(Class<A> clss) {
473 if (Node.class.isAssignableFrom(clss)) {
474 return (A) getJcrNode();
475 } else if (Source.class.isAssignableFrom(clss)) {
476 // try {
477 PipedOutputStream out = new PipedOutputStream();
478 PipedInputStream in;
479 try {
480 in = new PipedInputStream(out);
481 } catch (IOException e) {
482 throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
483 }
484
485 ForkJoinPool.commonPool().execute(() -> {
486 // try (PipedOutputStream out = new PipedOutputStream(in)) {
487 try {
488 getJcrSession().exportDocumentView(jcrPath, out, true, false);
489 out.flush();
490 out.close();
491 } catch (IOException | RepositoryException e) {
492 throw new RuntimeException("Cannot export " + jcrPath + " in workspace " + jcrWorkspace, e);
493 }
494
495 });
496 return (A) new StreamSource(in);
497 // } catch (IOException e) {
498 // throw new RuntimeException("Cannot adapt " + JcrContent.this + " to " + clss, e);
499 // }
500 } else {
501 return super.adapt(clss);
502 }
503 }
504
505 @SuppressWarnings("unchecked")
506 @Override
507 public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
508 try {
509 if (InputStream.class.isAssignableFrom(clss)) {
510 Node node = getJcrNode();
511 // System.out.println(node.getSession());
512 if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
513 return (C) JcrUtils.getFileAsStream(node);
514 }
515 } else if (OutputStream.class.isAssignableFrom(clss)) {
516 Node node = openForEdit();
517 // System.out.println(node.getSession());
518 if (Jcr.isNodeType(node, NodeType.NT_FILE)) {
519 Node content = node.getNode(Node.JCR_CONTENT);
520 AsyncPipedOutputStream out = new AsyncPipedOutputStream();
521
522 ValueFactory valueFactory = getJcrSession().getValueFactory();
523 out.asyncRead((in) -> {
524 try {
525 Binary binary = valueFactory.createBinary(in);
526 content.setProperty(Property.JCR_DATA, binary);
527 saveEditedNode(node);
528 } catch (RepositoryException e) {
529 throw new JcrException(
530 "Cannot create binary in " + jcrPath + " in workspace " + jcrWorkspace, e);
531 }
532 });
533 return (C) out;
534 }
535 }
536 } catch (RepositoryException e) {
537 throw new JcrException("Cannot open " + jcrPath + " in workspace " + jcrWorkspace, e);
538 }
539 return super.open(clss);
540 }
541
542 @Override
543 public JcrContentProvider getProvider() {
544 return provider;
545 }
546
547 @Override
548 public String getSessionLocalId() {
549 try {
550 return getJcrNode().getIdentifier();
551 } catch (RepositoryException e) {
552 throw new JcrException("Cannot get identifier for " + getJcrNode(), e);
553 }
554 }
555
556 /*
557 * TYPING
558 */
559
560 static Object convertSingleValue(Value value) throws JcrException, IllegalArgumentException {
561 try {
562 switch (value.getType()) {
563 case PropertyType.STRING:
564 return value.getString();
565 case PropertyType.DOUBLE:
566 return (Double) value.getDouble();
567 case PropertyType.LONG:
568 return (Long) value.getLong();
569 case PropertyType.BOOLEAN:
570 return (Boolean) value.getBoolean();
571 case PropertyType.DATE:
572 Calendar calendar = value.getDate();
573 return calendar.toInstant();
574 case PropertyType.BINARY:
575 throw new IllegalArgumentException("Binary is not supported as an attribute");
576 default:
577 return value.getString();
578 }
579 } catch (RepositoryException e) {
580 throw new JcrException("Cannot convert " + value + " to an object.", e);
581 }
582 }
583
584 static Value convertSingleObject(ValueFactory factory, Object value) {
585 if (value instanceof String string) {
586 return factory.createValue(string);
587 } else if (value instanceof Double dbl) {
588 return factory.createValue(dbl);
589 } else if (value instanceof Float flt) {
590 return factory.createValue(flt);
591 } else if (value instanceof Long lng) {
592 return factory.createValue(lng);
593 } else if (value instanceof Integer intg) {
594 return factory.createValue(intg);
595 } else if (value instanceof Boolean bool) {
596 return factory.createValue(bool);
597 } else if (value instanceof Instant instant) {
598 GregorianCalendar calendar = new GregorianCalendar();
599 calendar.setTime(Date.from(instant));
600 return factory.createValue(calendar);
601 } else {
602 // TODO or use String by default?
603 throw new IllegalArgumentException("Unsupported value " + value.getClass());
604 }
605 }
606
607 @Override
608 public Class<?> getType(QName key) {
609 Node node = getJcrNode();
610 String p = NamespaceUtils.toFullyQualified(key);
611 try {
612 if (node.hasProperty(p)) {
613 Property property = node.getProperty(p);
614 return switch (property.getType()) {
615 case PropertyType.STRING:
616 case PropertyType.NAME:
617 case PropertyType.PATH:
618 case PropertyType.DECIMAL:
619 yield String.class;
620 case PropertyType.LONG:
621 yield Long.class;
622 case PropertyType.DOUBLE:
623 yield Double.class;
624 case PropertyType.BOOLEAN:
625 yield Boolean.class;
626 case PropertyType.DATE:
627 yield Instant.class;
628 case PropertyType.WEAKREFERENCE:
629 case PropertyType.REFERENCE:
630 yield UUID.class;
631 default:
632 yield Object.class;
633 };
634 } else {
635 // TODO does it make sense?
636 return Object.class;
637 }
638 } catch (RepositoryException e) {
639 throw new JcrException("Cannot get type of property " + p + " of " + jcrPath + " in " + jcrWorkspace, e);
640 }
641 }
642
643 @Override
644 public List<QName> getContentClasses() {
645 try {
646 Node context = getJcrNode();
647
648 List<QName> res = new ArrayList<>();
649 // primary node type
650 NodeType primaryType = context.getPrimaryNodeType();
651 res.add(nodeTypeToQName(primaryType));
652 if (primaryType.getName().equals(NodeType.NT_FOLDER))
653 res.add(DName.collection.qName());
654
655 Set<QName> secondaryTypes = new TreeSet<>(NamespaceUtils.QNAME_COMPARATOR);
656 for (NodeType mixinType : context.getMixinNodeTypes()) {
657 secondaryTypes.add(nodeTypeToQName(mixinType));
658 }
659 for (NodeType superType : primaryType.getDeclaredSupertypes()) {
660 secondaryTypes.add(nodeTypeToQName(superType));
661 }
662 // mixins
663 for (NodeType mixinType : context.getMixinNodeTypes()) {
664 for (NodeType superType : mixinType.getDeclaredSupertypes()) {
665 secondaryTypes.add(nodeTypeToQName(superType));
666 }
667 }
668 res.addAll(secondaryTypes);
669 return res;
670 } catch (RepositoryException e) {
671 throw new JcrException("Cannot list node types from " + getJcrNode(), e);
672 }
673 }
674
675 private QName nodeTypeToQName(NodeType nodeType) {
676 String name = nodeType.getName();
677 return NamespaceUtils.parsePrefixedName(provider, name);
678 // return QName.valueOf(name);
679 }
680
681 /*
682 * COMMON UTILITIES
683 */
684 protected Session getJcrSession() {
685 Session s = provider.getJcrSession(getSession(), jcrWorkspace);
686 // if (getSession().isEditing())
687 // try {
688 // s.refresh(false);
689 // } catch (RepositoryException e) {
690 // throw new JcrException("Cannot refresh session", e);
691 // }
692 return s;
693 }
694
695 protected Node getJcrNode() {
696 try {
697 if (caching) {
698 synchronized (this) {
699 if (lastRetrievingThread != Thread.currentThread()) {
700 cachedNode = getJcrSession().getNode(jcrPath);
701 lastRetrievingThread = Thread.currentThread();
702 }
703 return cachedNode;
704 }
705 } else {
706 return getJcrSession().getNode(jcrPath);
707 }
708 } catch (RepositoryException e) {
709 throw new JcrException("Cannot retrieve " + jcrPath + " from workspace " + jcrWorkspace, e);
710 }
711 }
712
713 /*
714 * STATIC UTLITIES
715 */
716 public static Content nodeToContent(Node node) {
717 if (node == null)
718 return null;
719 try {
720 ProvidedSession contentSession = (ProvidedSession) node.getSession()
721 .getAttribute(ProvidedSession.class.getName());
722 if (contentSession == null)
723 throw new IllegalArgumentException(
724 "Cannot adapt " + node + " to content, because it was not loaded from a content session");
725 return contentSession.get(Content.ROOT_PATH + CmsConstants.SYS_WORKSPACE + node.getPath());
726 } catch (RepositoryException e) {
727 throw new JcrException("Cannot adapt " + node + " to a content", e);
728 }
729 }
730
731 /*
732 * CONTENT ITERATOR
733 */
734
735 class JcrContentIterator implements Iterator<Content> {
736 private final NodeIterator nodeIterator;
737 // we keep track in order to be able to delete it
738 private JcrContent current = null;
739
740 protected JcrContentIterator(NodeIterator nodeIterator) {
741 this.nodeIterator = nodeIterator;
742 }
743
744 @Override
745 public boolean hasNext() {
746 return nodeIterator.hasNext();
747 }
748
749 @Override
750 public Content next() {
751 current = new JcrContent(getSession(), provider, jcrWorkspace, Jcr.getPath(nodeIterator.nextNode()));
752 return current;
753 }
754
755 @Override
756 public void remove() {
757 if (current != null) {
758 Jcr.remove(current.getJcrNode());
759 }
760 }
761
762 }
763
764 }