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