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
.DName
;
34 import org
.argeo
.api
.acr
.NamespaceUtils
;
35 import org
.argeo
.api
.acr
.spi
.ProvidedContent
;
36 import org
.argeo
.api
.acr
.spi
.ProvidedSession
;
37 import org
.argeo
.api
.cms
.CmsLog
;
38 import org
.argeo
.jcr
.Jcr
;
39 import org
.argeo
.jcr
.JcrException
;
40 import org
.argeo
.jcr
.JcrUtils
;
41 import org
.w3c
.dom
.Attr
;
42 import org
.w3c
.dom
.Document
;
43 import org
.w3c
.dom
.Element
;
44 import org
.w3c
.dom
.NamedNodeMap
;
45 import org
.w3c
.dom
.NodeList
;
46 import org
.xml
.sax
.SAXException
;
48 /** Utilities around integration between JCR and ACR. */
49 public class JcrContentUtils
{
50 private final static CmsLog log
= CmsLog
.getLog(JcrContentUtils
.class);
52 public static void copyFiles(Node folder
, Content collection
, String
... additionalCollectionTypes
) {
54 log
.debug("Copy collection " + collection
);
56 NamespaceContext jcrNamespaceContext
= new JcrSessionNamespaceContext(folder
.getSession());
58 nodes
: for (NodeIterator it
= folder
.getNodes(); it
.hasNext();) {
59 Node node
= it
.nextNode();
60 String name
= node
.getName();
61 if (node
.isNodeType(NodeType
.NT_FILE
)) {
62 Content file
= collection
.anyOrAddChild(new ContentName(name
));
63 try (InputStream in
= JcrUtils
.getFileAsStream(node
)) {
64 file
.write(InputStream
.class).complete(in
);
66 } else if (node
.isNodeType(NodeType
.NT_FOLDER
)) {
67 Content subCol
= collection
.add(name
, DName
.collection
.qName());
68 copyFiles(node
, subCol
, additionalCollectionTypes
);
70 List
<QName
> contentClasses
= typesAsContentClasses(node
, jcrNamespaceContext
);
71 for (String collectionType
: additionalCollectionTypes
) {
72 if (node
.isNodeType(collectionType
)) {
73 contentClasses
.add(DName
.collection
.qName());
74 Content subCol
= collection
.add(name
,
75 contentClasses
.toArray(new QName
[contentClasses
.size()]));
76 setAttributes(node
, subCol
, jcrNamespaceContext
);
77 // setContentClasses(node, subCol, jcrNamespaceContext);
78 copyFiles(node
, subCol
, additionalCollectionTypes
);
83 QName qName
= NamespaceUtils
.parsePrefixedName(name
);
84 // if (NamespaceUtils.hasNamespace(qName)) {
85 if (node
.getIndex() > 1) {
86 log
.warn("Same name siblings not supported, skipping " + node
);
89 Content content
= collection
.add(qName
, contentClasses
.toArray(new QName
[contentClasses
.size()]));
90 Source source
= toSource(node
);
91 ((ProvidedContent
) content
).getSession().edit((s
) -> {
92 ((ProvidedSession
) s
).notifyModification((ProvidedContent
) content
);
93 content
.write(Source
.class).complete(source
);
95 // //setContentClasses(node, content, jcrNamespaceContext);
96 // } catch (RepositoryException e) {
97 // // TODO Auto-generated catch block
98 // e.printStackTrace();
100 }).toCompletableFuture().join();
101 setAttributes(node
, content
, jcrNamespaceContext
);
105 // log.debug(() -> "Ignored " + node);
110 } catch (RepositoryException e
) {
111 throw new JcrException("Cannot copy files from " + folder
+ " to " + collection
, e
);
112 } catch (IOException e
) {
113 throw new RuntimeException("Cannot copy files from " + folder
+ " to " + collection
, e
);
117 private static Source
toSource(Node node
) throws RepositoryException
{
119 // try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
120 // node.getSession().exportDocumentView(node.getPath(), out, true, false);
121 // System.out.println(new String(out.toByteArray(), StandardCharsets.UTF_8));
122 //// DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
123 //// Document document;
124 //// try (ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray())) {
125 //// document = documentBuilder.parse(in);
127 //// cleanJcrDom(document);
128 //// return new DOMSource(document);
129 // } catch (IOException e) {
130 // throw new RuntimeException(e);
133 try (PipedInputStream in
= new PipedInputStream();) {
135 CompletableFuture
<Document
> toDo
= CompletableFuture
.supplyAsync(() -> {
137 DocumentBuilder documentBuilder
= DocumentBuilderFactory
.newNSInstance().newDocumentBuilder();
138 return documentBuilder
.parse(in
);
139 } catch (ParserConfigurationException
| SAXException
| IOException e
) {
140 throw new RuntimeException("Cannot parse", e
);
145 try (PipedOutputStream out
= new PipedOutputStream(in
)) {
146 node
.getSession().exportDocumentView(node
.getPath(), out
, true, false);
147 } catch (IOException
| RepositoryException e
) {
148 throw new RuntimeException("Cannot export " + node
+ " in workspace " + Jcr
.getWorkspaceName(node
), e
);
150 Document document
= toDo
.get();
151 cleanJcrDom(document
);
152 return new DOMSource(document
);
153 } catch (IOException
| InterruptedException
| ExecutionException e1
) {
154 throw new RuntimeException("Cannot parse", e1
);
159 public static void setAttributes(Node source
, Content target
, NamespaceContext jcrNamespaceContext
)
160 throws RepositoryException
{
161 properties
: for (PropertyIterator pit
= source
.getProperties(); pit
.hasNext();) {
162 Property p
= pit
.nextProperty();
163 // TODO migrate JCR title, last modified, etc. ?
164 if (p
.getName().startsWith("jcr:"))
166 if (p
.isMultiple()) {
167 List
<String
> attr
= new ArrayList
<>();
168 for (Value value
: p
.getValues()) {
169 attr
.add(value
.getString());
171 target
.put(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, p
.getName()), attr
);
173 target
.put(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, p
.getName()), p
.getString());
178 public static List
<QName
> typesAsContentClasses(Node source
, NamespaceContext jcrNamespaceContext
)
179 throws RepositoryException
{
181 List
<QName
> contentClasses
= new ArrayList
<>();
183 .add(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, source
.getPrimaryNodeType().getName()));
184 for (NodeType nodeType
: source
.getMixinNodeTypes()) {
185 contentClasses
.add(NamespaceUtils
.parsePrefixedName(jcrNamespaceContext
, nodeType
.getName()));
187 // filter out JCR types
188 for (Iterator
<QName
> it
= contentClasses
.iterator(); it
.hasNext();) {
189 QName type
= it
.next();
190 if (type
.getNamespaceURI().equals(JCR_NT_NAMESPACE_URI
)
191 || type
.getNamespaceURI().equals(JCR_MIX_NAMESPACE_URI
)) {
195 // target.addContentClasses(contentClasses.toArray(new
196 // QName[contentClasses.size()]));
197 return contentClasses
;
200 static final String JCR_NAMESPACE_URI
= "http://www.jcp.org/jcr/1.0";
201 static final String JCR_NT_NAMESPACE_URI
= "http://www.jcp.org/jcr/nt/1.0";
202 static final String JCR_MIX_NAMESPACE_URI
= "http://www.jcp.org/jcr/mix/1.0";
204 public static void cleanJcrDom(Document document
) {
205 Element documentElement
= document
.getDocumentElement();
206 Set
<String
> namespaceUris
= new HashSet
<>();
207 cleanJcrDom(documentElement
, namespaceUris
);
209 // remove unused namespaces
210 NamedNodeMap attrs
= documentElement
.getAttributes();
211 Set
<Attr
> toRemove
= new HashSet
<>();
212 for (int i
= 0; i
< attrs
.getLength(); i
++) {
213 Attr attr
= (Attr
) attrs
.item(i
);
214 // log.debug("Check "+i+" " + attr);
215 String prefix
= attr
.getPrefix();
216 if (prefix
!= null && prefix
.equals(XMLConstants
.XMLNS_ATTRIBUTE
)) {
217 String namespaceUri
= attr
.getValue();
218 if (!namespaceUris
.contains(namespaceUri
)) {
220 // log.debug("Removing "+i+" " + namespaceUri);
224 for (Attr attr
: toRemove
)
225 documentElement
.removeAttributeNode(attr
);
229 private static void cleanJcrDom(Element element
, Set
<String
> namespaceUris
) {
230 NodeList children
= element
.getElementsByTagName("*");
231 for (int i
= 0; i
< children
.getLength(); i
++) {
232 Element child
= (Element
) children
.item(i
);
233 if (!namespaceUris
.contains(child
.getNamespaceURI()))
234 namespaceUris
.add(child
.getNamespaceURI());
235 cleanJcrDom(child
, namespaceUris
);
238 NamedNodeMap attrs
= element
.getAttributes();
239 attributes
: for (int i
= 0; i
< attrs
.getLength(); i
++) {
240 Attr attr
= (Attr
) attrs
.item(i
);
241 String namespaceUri
= attr
.getNamespaceURI();
242 if (namespaceUri
== null)
244 if (JCR_NAMESPACE_URI
.equals(namespaceUri
)) {
245 // FIXME probably wrong to change attributes length
246 element
.removeAttributeNode(attr
);
249 if (!namespaceUris
.contains(namespaceUri
))
250 namespaceUris
.add(attr
.getNamespaceURI());
257 private JcrContentUtils() {