1 package org
.argeo
.cms
.jcr
.acr
;
3 import java
.io
.Closeable
;
4 import java
.io
.IOException
;
5 import java
.io
.InputStream
;
6 import java
.io
.PipedInputStream
;
7 import java
.io
.PipedOutputStream
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Calendar
;
10 import java
.util
.HashSet
;
11 import java
.util
.Iterator
;
12 import java
.util
.List
;
13 import java
.util
.Optional
;
15 import java
.util
.TreeSet
;
16 import java
.util
.concurrent
.ForkJoinPool
;
18 import javax
.jcr
.Node
;
19 import javax
.jcr
.NodeIterator
;
20 import javax
.jcr
.Property
;
21 import javax
.jcr
.PropertyIterator
;
22 import javax
.jcr
.PropertyType
;
23 import javax
.jcr
.RepositoryException
;
24 import javax
.jcr
.Value
;
25 import javax
.jcr
.nodetype
.NodeType
;
26 import javax
.xml
.namespace
.QName
;
27 import javax
.xml
.transform
.Source
;
28 import javax
.xml
.transform
.stream
.StreamSource
;
30 import org
.argeo
.api
.acr
.Content
;
31 import org
.argeo
.api
.acr
.NamespaceUtils
;
32 import org
.argeo
.api
.acr
.spi
.ContentProvider
;
33 import org
.argeo
.api
.acr
.spi
.ProvidedSession
;
34 import org
.argeo
.api
.cms
.CmsConstants
;
35 import org
.argeo
.cms
.acr
.AbstractContent
;
36 import org
.argeo
.cms
.acr
.ContentUtils
;
37 import org
.argeo
.jcr
.Jcr
;
38 import org
.argeo
.jcr
.JcrException
;
39 import org
.argeo
.jcr
.JcrUtils
;
41 /** A JCR {@link Node} accessed as {@link Content}. */
42 public class JcrContent
extends AbstractContent
{
43 // private Node jcrNode;
45 private JcrContentProvider provider
;
47 private String jcrWorkspace
;
48 private String jcrPath
;
50 protected JcrContent(ProvidedSession session
, JcrContentProvider provider
, String jcrWorkspace
, String jcrPath
) {
52 this.provider
= provider
;
53 this.jcrWorkspace
= jcrWorkspace
;
54 this.jcrPath
= jcrPath
;
58 public QName
getName() {
59 String name
= Jcr
.getName(getJcrNode());
60 if (name
.equals("")) {// root
61 String mountPath
= provider
.getMountPath();
62 name
= ContentUtils
.getParentPath(mountPath
)[1];
63 // name = Jcr.getWorkspaceName(getJcrNode());
65 return NamespaceUtils
.parsePrefixedName(provider
, name
);
68 @SuppressWarnings("unchecked")
70 public <A
> Optional
<A
> get(QName key
, Class
<A
> clss
) {
71 if (isDefaultAttrTypeRequested(clss
)) {
72 return Optional
.of((A
) get(getJcrNode(), key
.toString()));
74 return Optional
.of((A
) Jcr
.get(getJcrNode(), key
.toString()));
78 public Iterator
<Content
> iterator() {
80 return new JcrContentIterator(getJcrNode().getNodes());
81 } catch (RepositoryException e
) {
82 throw new JcrException("Cannot list children of " + getJcrNode(), e
);
87 protected Iterable
<QName
> keys() {
89 Set
<QName
> keys
= new HashSet
<>();
90 for (PropertyIterator propertyIterator
= getJcrNode().getProperties(); propertyIterator
.hasNext();) {
91 Property property
= propertyIterator
.nextProperty();
92 // TODO convert standard names
93 // TODO skip technical properties
94 QName name
= NamespaceUtils
.parsePrefixedName(provider
, property
.getName());
98 } catch (RepositoryException e
) {
99 throw new JcrException("Cannot list properties of " + getJcrNode(), e
);
103 public Node
getJcrNode() {
106 return provider
.getJcrSession(getSession(), jcrWorkspace
).getNode(jcrPath
);
107 } catch (RepositoryException e
) {
108 throw new JcrException("Cannot retrieve " + jcrPath
+ " from workspace " + jcrWorkspace
, e
);
112 /** Cast to a standard Java object. */
113 static Object
get(Node node
, String property
) {
115 Property p
= node
.getProperty(property
);
116 if (p
.isMultiple()) {
117 Value
[] values
= p
.getValues();
118 List
<Object
> lst
= new ArrayList
<>();
119 for (Value value
: values
) {
120 lst
.add(convertSingleValue(value
));
124 Value value
= node
.getProperty(property
).getValue();
125 return convertSingleValue(value
);
127 } catch (RepositoryException e
) {
128 throw new JcrException("Cannot cast value from " + property
+ " of node " + node
, e
);
132 static Object
convertSingleValue(Value value
) throws RepositoryException
{
133 switch (value
.getType()) {
134 case PropertyType
.STRING
:
135 return value
.getString();
136 case PropertyType
.DOUBLE
:
137 return (Double
) value
.getDouble();
138 case PropertyType
.LONG
:
139 return (Long
) value
.getLong();
140 case PropertyType
.BOOLEAN
:
141 return (Boolean
) value
.getBoolean();
142 case PropertyType
.DATE
:
143 Calendar calendar
= value
.getDate();
144 return calendar
.toInstant();
145 case PropertyType
.BINARY
:
146 throw new IllegalArgumentException("Binary is not supported as an attribute");
148 return value
.getString();
152 class JcrContentIterator
implements Iterator
<Content
> {
153 private final NodeIterator nodeIterator
;
154 // we keep track in order to be able to delete it
155 private JcrContent current
= null;
157 protected JcrContentIterator(NodeIterator nodeIterator
) {
158 this.nodeIterator
= nodeIterator
;
162 public boolean hasNext() {
163 return nodeIterator
.hasNext();
167 public Content
next() {
168 current
= new JcrContent(getSession(), provider
, jcrWorkspace
, Jcr
.getPath(nodeIterator
.nextNode()));
173 public void remove() {
174 if (current
!= null) {
175 Jcr
.remove(current
.getJcrNode());
182 public String
getPath() {
184 // Note: it is important to to use the default way (recursing through parents),
185 // since the session may not have access to parent nodes
186 return ContentUtils
.ROOT_SLASH
+ jcrWorkspace
+ getJcrNode().getPath();
187 } catch (RepositoryException e
) {
188 throw new JcrException("Cannot get depth of " + getJcrNode(), e
);
193 public int getDepth() {
195 return getJcrNode().getDepth() + 1;
196 } catch (RepositoryException e
) {
197 throw new JcrException("Cannot get depth of " + getJcrNode(), e
);
202 public Content
getParent() {
203 if (Jcr
.isRoot(getJcrNode())) // root
205 return new JcrContent(getSession(), provider
, jcrWorkspace
, Jcr
.getParentPath(getJcrNode()));
209 public Content
add(QName name
, QName
... classes
) {
210 if (classes
.length
> 0) {
211 QName primaryType
= classes
[0];
212 Node child
= Jcr
.addNode(getJcrNode(), name
.toString(), primaryType
.toString());
213 for (int i
= 1; i
< classes
.length
; i
++) {
215 child
.addMixin(classes
[i
].toString());
216 } catch (RepositoryException e
) {
217 throw new JcrException("Cannot add child to " + getJcrNode(), e
);
222 Jcr
.addNode(getJcrNode(), name
.toString(), NodeType
.NT_UNSTRUCTURED
);
228 public void remove() {
229 Jcr
.remove(getJcrNode());
233 protected void removeAttr(QName key
) {
234 Property property
= Jcr
.getProperty(getJcrNode(), key
.toString());
235 if (property
!= null) {
238 } catch (RepositoryException e
) {
239 throw new JcrException("Cannot remove property " + key
+ " from " + getJcrNode(), e
);
247 return provider
.getJcrSession(getSession(), jcrWorkspace
).itemExists(jcrPath
);
248 } catch (RepositoryException e
) {
249 throw new JcrException("Cannot check whether " + jcrPath
+ " exists", e
);
256 @SuppressWarnings("unchecked")
257 public <A
> A
adapt(Class
<A
> clss
) {
258 if (Source
.class.isAssignableFrom(clss
)) {
260 PipedOutputStream out
= new PipedOutputStream();
263 in
= new PipedInputStream(out
);
264 } catch (IOException e
) {
265 throw new RuntimeException("Cannot export " + jcrPath
+ " in workspace " + jcrWorkspace
, e
);
268 ForkJoinPool
.commonPool().execute(() -> {
269 // try (PipedOutputStream out = new PipedOutputStream(in)) {
271 provider
.getJcrSession(getSession(), jcrWorkspace
).exportDocumentView(jcrPath
, out
, true, false);
274 } catch (IOException
| RepositoryException e
) {
275 throw new RuntimeException("Cannot export " + jcrPath
+ " in workspace " + jcrWorkspace
, e
);
279 return (A
) new StreamSource(in
);
280 // } catch (IOException e) {
281 // throw new RuntimeException("Cannot adapt " + JcrContent.this + " to " + clss, e);
285 return super.adapt(clss
);
288 @SuppressWarnings("unchecked")
290 public <C
extends Closeable
> C
open(Class
<C
> clss
) throws IOException
, IllegalArgumentException
{
291 if (InputStream
.class.isAssignableFrom(clss
)) {
292 Node node
= getJcrNode();
293 if (Jcr
.isNodeType(node
, NodeType
.NT_FILE
)) {
295 return (C
) JcrUtils
.getFileAsStream(node
);
296 } catch (RepositoryException e
) {
297 throw new JcrException("Cannot open " + jcrPath
+ " in workspace " + jcrWorkspace
, e
);
301 return super.open(clss
);
305 public ContentProvider
getProvider() {
310 public String
getSessionLocalId() {
312 return getJcrNode().getIdentifier();
313 } catch (RepositoryException e
) {
314 throw new JcrException("Cannot get identifier for " + getJcrNode(), e
);
322 public List
<QName
> getContentClasses() {
324 // Node node = getJcrNode();
325 // List<QName> res = new ArrayList<>();
326 // res.add(nodeTypeToQName(node.getPrimaryNodeType()));
327 // for (NodeType mixin : node.getMixinNodeTypes()) {
328 // res.add(nodeTypeToQName(mixin));
331 Node context
= getJcrNode();
333 List
<QName
> res
= new ArrayList
<>();
335 NodeType primaryType
= context
.getPrimaryNodeType();
336 res
.add(nodeTypeToQName(primaryType
));
338 Set
<QName
> secondaryTypes
= new TreeSet
<>(NamespaceUtils
.QNAME_COMPARATOR
);
339 for (NodeType mixinType
: context
.getMixinNodeTypes()) {
340 secondaryTypes
.add(nodeTypeToQName(mixinType
));
342 for (NodeType superType
: primaryType
.getDeclaredSupertypes()) {
343 secondaryTypes
.add(nodeTypeToQName(superType
));
346 for (NodeType mixinType
: context
.getMixinNodeTypes()) {
347 for (NodeType superType
: mixinType
.getDeclaredSupertypes()) {
348 secondaryTypes
.add(nodeTypeToQName(superType
));
352 // if (context.isNodeType(EntityType.entity.get())) {
353 // if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
354 // String entityTypeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
355 // if (byType.containsKey(entityTypeName)) {
356 // types.add(entityTypeName);
360 res
.addAll(secondaryTypes
);
362 } catch (RepositoryException e
) {
363 throw new JcrException("Cannot list node types from " + getJcrNode(), e
);
367 private QName
nodeTypeToQName(NodeType nodeType
) {
368 String name
= nodeType
.getName();
369 return QName
.valueOf(name
);
373 public int getSiblingIndex() {
374 return Jcr
.getIndex(getJcrNode());
380 public static Content
nodeToContent(Node node
) {
384 ProvidedSession contentSession
= (ProvidedSession
) node
.getSession()
385 .getAttribute(ProvidedSession
.class.getName());
386 if (contentSession
== null)
387 throw new IllegalArgumentException(
388 "Cannot adapt " + node
+ " to content, because it was not loaded from a content session");
389 return contentSession
.get(ContentUtils
.SLASH
+ CmsConstants
.SYS_WORKSPACE
+ node
.getPath());
390 } catch (RepositoryException e
) {
391 throw new JcrException("Cannot adapt " + node
+ " to a content", e
);