]> git.argeo.org Git - gpl/argeo-jcr.git/blob - org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentUtils.java
Optimisation and caching
[gpl/argeo-jcr.git] / org.argeo.cms.jcr / src / org / argeo / cms / jcr / acr / JcrContentUtils.java
1 package org.argeo.cms.jcr.acr;
2
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;
11 import java.util.Set;
12 import java.util.concurrent.CompletableFuture;
13 import java.util.concurrent.ExecutionException;
14
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;
30
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;
47
48 /** Utilities around integration between JCR and ACR. */
49 public class JcrContentUtils {
50 private final static CmsLog log = CmsLog.getLog(JcrContentUtils.class);
51
52 public static void copyFiles(Node folder, Content collection, String... additionalCollectionTypes) {
53 try {
54 log.debug("Copy collection " + collection);
55
56 NamespaceContext jcrNamespaceContext = new JcrSessionNamespaceContext(folder.getSession());
57
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);
65 }
66 } else if (node.isNodeType(NodeType.NT_FOLDER)) {
67 Content subCol = collection.add(name, DName.collection.qName());
68 copyFiles(node, subCol, additionalCollectionTypes);
69 } else {
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);
79 continue nodes;
80 }
81 }
82
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);
87 continue nodes;
88 }
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);
94 // try {
95 // //setContentClasses(node, content, jcrNamespaceContext);
96 // } catch (RepositoryException e) {
97 // // TODO Auto-generated catch block
98 // e.printStackTrace();
99 // }
100 }).toCompletableFuture().join();
101 setAttributes(node, content, jcrNamespaceContext);
102
103 // } else {
104 // // ignore
105 // log.debug(() -> "Ignored " + node);
106 // continue nodes;
107 // }
108 }
109 }
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);
114 }
115 }
116
117 private static Source toSource(Node node) throws RepositoryException {
118
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);
126 //// }
127 //// cleanJcrDom(document);
128 //// return new DOMSource(document);
129 // } catch (IOException e) {
130 // throw new RuntimeException(e);
131 // }
132
133 try (PipedInputStream in = new PipedInputStream();) {
134
135 CompletableFuture<Document> toDo = CompletableFuture.supplyAsync(() -> {
136 try {
137 DocumentBuilder documentBuilder = DocumentBuilderFactory.newNSInstance().newDocumentBuilder();
138 return documentBuilder.parse(in);
139 } catch (ParserConfigurationException | SAXException | IOException e) {
140 throw new RuntimeException("Cannot parse", e);
141 }
142 });
143
144 // TODO optimise
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);
149 }
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);
155 }
156
157 }
158
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:"))
165 continue properties;
166 if (p.isMultiple()) {
167 List<String> attr = new ArrayList<>();
168 for (Value value : p.getValues()) {
169 attr.add(value.getString());
170 }
171 target.put(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, p.getName()), attr);
172 } else {
173 target.put(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, p.getName()), p.getString());
174 }
175 }
176 }
177
178 public static List<QName> typesAsContentClasses(Node source, NamespaceContext jcrNamespaceContext)
179 throws RepositoryException {
180 // TODO super types?
181 List<QName> contentClasses = new ArrayList<>();
182 contentClasses
183 .add(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, source.getPrimaryNodeType().getName()));
184 for (NodeType nodeType : source.getMixinNodeTypes()) {
185 contentClasses.add(NamespaceUtils.parsePrefixedName(jcrNamespaceContext, nodeType.getName()));
186 }
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)) {
192 it.remove();
193 }
194 }
195 // target.addContentClasses(contentClasses.toArray(new
196 // QName[contentClasses.size()]));
197 return contentClasses;
198 }
199
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";
203
204 public static void cleanJcrDom(Document document) {
205 Element documentElement = document.getDocumentElement();
206 Set<String> namespaceUris = new HashSet<>();
207 cleanJcrDom(documentElement, namespaceUris);
208
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)) {
219 toRemove.add(attr);
220 // log.debug("Removing "+i+" " + namespaceUri);
221 }
222 }
223 }
224 for (Attr attr : toRemove)
225 documentElement.removeAttributeNode(attr);
226
227 }
228
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);
236 }
237
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)
243 continue attributes;
244 if (JCR_NAMESPACE_URI.equals(namespaceUri)) {
245 // FIXME probably wrong to change attributes length
246 element.removeAttributeNode(attr);
247 continue attributes;
248 }
249 if (!namespaceUris.contains(namespaceUri))
250 namespaceUris.add(attr.getNamespaceURI());
251
252 }
253
254 }
255
256 /** singleton */
257 private JcrContentUtils() {
258 }
259
260 }