1 package org
.argeo
.cms
.jcr
.acr
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.io
.PipedInputStream
;
6 import java
.io
.PipedOutputStream
;
7 import java
.util
.ArrayList
;
8 import java
.util
.HashSet
;
9 import java
.util
.Iterator
;
10 import java
.util
.List
;
12 import java
.util
.concurrent
.CompletableFuture
;
13 import java
.util
.concurrent
.ExecutionException
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.NodeIterator
;
17 import javax
.jcr
.Property
;
18 import javax
.jcr
.PropertyIterator
;
19 import javax
.jcr
.RepositoryException
;
20 import javax
.jcr
.Value
;
21 import javax
.jcr
.nodetype
.NodeType
;
22 import javax
.xml
.XMLConstants
;
23 import javax
.xml
.namespace
.NamespaceContext
;
24 import javax
.xml
.namespace
.QName
;
25 import javax
.xml
.parsers
.DocumentBuilder
;
26 import javax
.xml
.parsers
.DocumentBuilderFactory
;
27 import javax
.xml
.parsers
.ParserConfigurationException
;
28 import javax
.xml
.transform
.Source
;
29 import javax
.xml
.transform
.dom
.DOMSource
;
31 import org
.argeo
.api
.acr
.Content
;
32 import org
.argeo
.api
.acr
.ContentName
;
33 import org
.argeo
.api
.acr
.CrName
;
34 import org
.argeo
.api
.acr
.DName
;
35 import org
.argeo
.api
.acr
.NamespaceUtils
;
36 import org
.argeo
.api
.acr
.spi
.ProvidedContent
;
37 import org
.argeo
.api
.acr
.spi
.ProvidedSession
;
38 import org
.argeo
.api
.cms
.CmsLog
;
39 import org
.argeo
.jcr
.Jcr
;
40 import org
.argeo
.jcr
.JcrException
;
41 import org
.argeo
.jcr
.JcrUtils
;
42 import org
.w3c
.dom
.Attr
;
43 import org
.w3c
.dom
.Document
;
44 import org
.w3c
.dom
.Element
;
45 import org
.w3c
.dom
.NamedNodeMap
;
46 import org
.w3c
.dom
.NodeList
;
47 import org
.xml
.sax
.SAXException
;
49 /** Utilities around integration between JCR and ACR. */
50 public class JcrContentUtils
{
51 private final static CmsLog log
= CmsLog
.getLog(JcrContentUtils
.class);
53 public static void copyFiles(Node folder
, Content collection
, String
... additionalCollectionTypes
) {
55 log
.debug("Copy collection " + collection
);
57 NamespaceContext jcrNamespaceContext
= new JcrSessionNamespaceContext(folder
.getSession());
59 nodes
: for (NodeIterator it
= folder
.getNodes(); it
.hasNext();) {
60 Node node
= it
.nextNode();
61 String name
= node
.getName();
62 if (node
.isNodeType(NodeType
.NT_FILE
)) {
63 Content file
= collection
.anyOrAddChild(new ContentName(name
));
64 try (InputStream in
= JcrUtils
.getFileAsStream(node
)) {
65 file
.write(InputStream
.class).complete(in
);
67 } else if (node
.isNodeType(NodeType
.NT_FOLDER
)) {
68 Content subCol
= collection
.add(name
, DName
.collection
.qName());
69 copyFiles(node
, subCol
, additionalCollectionTypes
);
71 List
<QName
> contentClasses
= typesAsContentClasses(node
, jcrNamespaceContext
);
72 for (String collectionType
: additionalCollectionTypes
) {
73 if (node
.isNodeType(collectionType
)) {
74 contentClasses
.add(DName
.collection
.qName());
75 Content subCol
= collection
.add(name
,
76 contentClasses
.toArray(new QName
[contentClasses
.size()]));
77 setAttributes(node
, subCol
, jcrNamespaceContext
);
78 // setContentClasses(node, subCol, jcrNamespaceContext);
79 copyFiles(node
, subCol
, additionalCollectionTypes
);
84 QName qName
= NamespaceUtils
.parsePrefixedName(name
);
85 // if (NamespaceUtils.hasNamespace(qName)) {
86 if (node
.getIndex() > 1) {
87 log
.warn("Same name siblings not supported, skipping " + node
);
90 Content content
= collection
.add(qName
, contentClasses
.toArray(new QName
[contentClasses
.size()]));
91 Source source
= toSource(node
);
92 ((ProvidedContent
) content
).getSession().edit((s
) -> {
93 ((ProvidedSession
) s
).notifyModification((ProvidedContent
) content
);
94 content
.write(Source
.class).complete(source
);
96 // //setContentClasses(node, content, jcrNamespaceContext);
97 // } catch (RepositoryException e) {
98 // // TODO Auto-generated catch block
99 // e.printStackTrace();
101 }).toCompletableFuture().join();
102 setAttributes(node
, content
, jcrNamespaceContext
);
106 // log.debug(() -> "Ignored " + node);
111 } catch (RepositoryException e
) {
112 throw new JcrException("Cannot copy files from " + folder
+ " to " + collection
, e
);
113 } catch (IOException e
) {
114 throw new RuntimeException("Cannot copy files from " + folder
+ " to " + collection
, e
);
118 private static Source
toSource(Node node
) throws RepositoryException
{
120 // try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
121 // node.getSession().exportDocumentView(node.getPath(), out, true, false);
122 // System.out.println(new String(out.toByteArray(), StandardCharsets.UTF_8));
123 //// DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
124 //// Document document;
125 //// try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
126 //// document = documentBuilder.parse(in);
128 //// cleanJcrDom(document);
129 //// return new DOMSource(document);
130 // } catch (IOException e) {
131 // throw new RuntimeException(e);
134 try (PipedInputStream in
= new PipedInputStream();) {
136 CompletableFuture
<Document
> toDo
= CompletableFuture
.supplyAsync(() -> {
138 DocumentBuilder documentBuilder
= DocumentBuilderFactory
.newNSInstance().newDocumentBuilder();
139 return documentBuilder
.parse(in
);
140 } catch (ParserConfigurationException
| SAXException
| IOException e
) {
141 throw new RuntimeException("Cannot parse", e
);
146 try (PipedOutputStream out
= new PipedOutputStream(in
)) {
147 node
.getSession().exportDocumentView(node
.getPath(), out
, true, false);
148 } catch (IOException
| RepositoryException e
) {
149 throw new RuntimeException("Cannot export " + node
+ " in workspace " + Jcr
.getWorkspaceName(node
), e
);
151 Document document
= toDo
.get();
152 cleanJcrDom(document
);
153 return new DOMSource(document
);
154 } catch (IOException
| InterruptedException
| ExecutionException e1
) {
155 throw new RuntimeException("Cannot parse", e1
);
160 public static void setAttributes(Node source
, Content target
, NamespaceContext jcrNamespaceContext
)
161 throws RepositoryException
{
162 properties
: for (PropertyIterator pit
= source
.getProperties(); pit
.hasNext();) {
163 Property p
= pit
.nextProperty();
164 // TODO migrate JCR title, last modified, etc. ?
165 if (p
.getName().startsWith("jcr:"))
167 if (p
.isMultiple()) {
168 List
<String
> attr
= new ArrayList
<>();
169 for (Value value
: p
.getValues()) {
170 attr
.add(value
.getString());
172 target
.put(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, p
.getName()), attr
);
174 target
.put(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, p
.getName()), p
.getString());
179 public static List
<QName
> typesAsContentClasses(Node source
, NamespaceContext jcrNamespaceContext
)
180 throws RepositoryException
{
182 List
<QName
> contentClasses
= new ArrayList
<>();
184 .add(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, source
.getPrimaryNodeType().getName()));
185 for (NodeType nodeType
: source
.getMixinNodeTypes()) {
186 contentClasses
.add(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, nodeType
.getName()));
188 // filter out JCR types
189 for (Iterator
<QName
> it
= contentClasses
.iterator(); it
.hasNext();) {
190 QName type
= it
.next();
191 if (type
.getNamespaceURI().equals(JCR_NT_NAMESPACE_URI
)
192 || type
.getNamespaceURI().equals(JCR_MIX_NAMESPACE_URI
)) {
196 // target.addContentClasses(contentClasses.toArray(new
197 // QName[contentClasses.size()]));
198 return contentClasses
;
201 static final String JCR_NAMESPACE_URI
= "http://www.jcp.org/jcr/1.0";
202 static final String JCR_NT_NAMESPACE_URI
= "http://www.jcp.org/jcr/nt/1.0";
203 static final String JCR_MIX_NAMESPACE_URI
= "http://www.jcp.org/jcr/mix/1.0";
205 public static void cleanJcrDom(Document document
) {
206 Element documentElement
= document
.getDocumentElement();
207 Set
<String
> namespaceUris
= new HashSet
<>();
208 cleanJcrDom(documentElement
, namespaceUris
);
210 // remove unused namespaces
211 NamedNodeMap attrs
= documentElement
.getAttributes();
212 Set
<Attr
> toRemove
= new HashSet
<>();
213 for (int i
= 0; i
< attrs
.getLength(); i
++) {
214 Attr attr
= (Attr
) attrs
.item(i
);
215 // log.debug("Check "+i+" " + attr);
216 String prefix
= attr
.getPrefix();
217 if (prefix
!= null && prefix
.equals(XMLConstants
.XMLNS_ATTRIBUTE
)) {
218 String namespaceUri
= attr
.getValue();
219 if (!namespaceUris
.contains(namespaceUri
)) {
221 // log.debug("Removing "+i+" " + namespaceUri);
225 for (Attr attr
: toRemove
)
226 documentElement
.removeAttributeNode(attr
);
230 private static void cleanJcrDom(Element element
, Set
<String
> namespaceUris
) {
231 NodeList children
= element
.getElementsByTagName("*");
232 for (int i
= 0; i
< children
.getLength(); i
++) {
233 Element child
= (Element
) children
.item(i
);
234 if (!namespaceUris
.contains(child
.getNamespaceURI()))
235 namespaceUris
.add(child
.getNamespaceURI());
236 cleanJcrDom(child
, namespaceUris
);
239 NamedNodeMap attrs
= element
.getAttributes();
240 attributes
: for (int i
= 0; i
< attrs
.getLength(); i
++) {
241 Attr attr
= (Attr
) attrs
.item(i
);
242 String namespaceUri
= attr
.getNamespaceURI();
243 if (namespaceUri
== null)
245 if (JCR_NAMESPACE_URI
.equals(namespaceUri
)) {
246 // FIXME probably wrong to change attributes length
247 element
.removeAttributeNode(attr
);
250 if (!namespaceUris
.contains(namespaceUri
))
251 namespaceUris
.add(attr
.getNamespaceURI());
258 private JcrContentUtils() {