1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.oaipmh.util;
22
23 import static org.opencastproject.util.IoSupport.withResource;
24 import static org.opencastproject.util.data.Option.some;
25 import static org.opencastproject.util.data.functions.Misc.chuck;
26
27 import org.opencastproject.metadata.dublincore.DublinCore;
28 import org.opencastproject.util.XmlSafeParser;
29 import org.opencastproject.util.data.Function;
30 import org.opencastproject.util.data.Function0;
31 import org.opencastproject.util.data.Option;
32
33
34 import org.apache.commons.io.output.ByteArrayOutputStream;
35 import org.apache.commons.lang3.ObjectUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.w3c.dom.Attr;
38 import org.w3c.dom.Document;
39 import org.w3c.dom.Element;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42
43 import java.io.OutputStream;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collections;
47 import java.util.List;
48
49 import javax.xml.XMLConstants;
50 import javax.xml.parsers.DocumentBuilder;
51 import javax.xml.parsers.DocumentBuilderFactory;
52 import javax.xml.parsers.ParserConfigurationException;
53 import javax.xml.transform.OutputKeys;
54 import javax.xml.transform.Transformer;
55 import javax.xml.transform.TransformerException;
56 import javax.xml.transform.dom.DOMSource;
57 import javax.xml.transform.stream.StreamResult;
58
59
60
61
62
63
64
65 public abstract class XmlGen {
66 private final Document document;
67 private final Option<String> defaultNamespace;
68
69
70
71
72 public XmlGen(Option<String> defaultNamespace) {
73 document = createDocument();
74 this.defaultNamespace = defaultNamespace;
75 }
76
77 private Document createDocument() {
78 try {
79 DocumentBuilderFactory factory = XmlSafeParser.newDocumentBuilderFactory();
80 factory.setNamespaceAware(true);
81 DocumentBuilder builder = factory.newDocumentBuilder();
82 return builder.newDocument();
83 } catch (ParserConfigurationException e) {
84 return chuck(e);
85 }
86 }
87
88 private void write(OutputStream out) {
89 try {
90 Transformer transformer = XmlSafeParser.newTransformerFactory().newTransformer();
91 transformer.setOutputProperty(OutputKeys.METHOD, "xml");
92 transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
93 transformer.setOutputProperty(OutputKeys.INDENT, "yes");
94 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
95 DOMSource source = new DOMSource(document);
96 StreamResult result = new StreamResult(out);
97 transformer.transform(source, result);
98 } catch (TransformerException e) {
99 throw new RuntimeException(e);
100 }
101 }
102
103
104
105
106 public void generate(OutputStream out) {
107 generate();
108 write(out);
109 }
110
111
112
113
114 public Document generate() {
115 final Node node = document.importNode(create(), true);
116 final Element docElem = document.getDocumentElement();
117 if (docElem != null) {
118 document.removeChild(docElem);
119 }
120 document.appendChild(node);
121 return document;
122 }
123
124
125 public String generateAsString() {
126 return withResource(new ByteArrayOutputStream(), new Function<ByteArrayOutputStream, String>() {
127 @Override public String apply(ByteArrayOutputStream out) {
128 generate(out);
129 return out.toString();
130 }
131 });
132 }
133
134
135
136
137 public abstract Element create();
138
139
140
141 protected Namespace ns(String prefix, String namespace) {
142 return new Namespace(prefix, namespace);
143 }
144
145 protected Node schemaLocation(String location) {
146 return $a("xsi:schemaLocation", location);
147 }
148
149
150
151 protected Node $langNode(String language) {
152 if (StringUtils.isBlank(language) || DublinCore.LANGUAGE_UNDEFINED.equals(language)
153 || DublinCore.LANGUAGE_ANY.equals(language))
154 return nodeZero();
155
156 Attr a = document.createAttributeNS(XMLConstants.XML_NS_URI, "xml:lang");
157 a.setValue(language);
158 return a;
159 }
160
161 protected Node $a(String name, String value) {
162 Attr a = document.createAttribute(name);
163 a.setValue(value);
164 return a;
165 }
166
167 protected Node $aBlank(String name, String value) {
168 if (StringUtils.isNotBlank(value)) {
169 Attr a = document.createAttribute(name);
170 a.setValue(value);
171 return a;
172 } else {
173 return nodeZero();
174 }
175 }
176
177 protected Node $aSome(final String name, final Option<String> value) {
178 return value.fold(new Option.Match<String, Node>() {
179 @Override
180 public Node some(String value) {
181 Attr a = document.createAttribute(name);
182 a.setValue(value);
183 return a;
184 }
185
186 @Override
187 public Node none() {
188 return nodeZero();
189 }
190 });
191 }
192
193 protected Element $e(String qname, Option<String> namespace, List<Node> nodes) {
194 return appendTo(createElemNs(namespace, qname), nodes);
195 }
196
197
198
199
200
201 protected Element $e(String qname, Option<String> namespace, NodeList nodes) {
202 return appendTo(createElemNs(namespace, qname), nodes);
203 }
204
205 protected Element $e(String qname, Option<String> namespace, Node... nodes) {
206 return $e(qname, namespace, Arrays.asList(nodes));
207 }
208
209 protected Element $e(String name, Node... nodes) {
210 return $e(name, defaultNamespace, Arrays.asList(nodes));
211 }
212
213 protected Element $e(String name, List<Node> nodes) {
214 return $e(name, defaultNamespace, Collections.unmodifiableList(nodes));
215 }
216
217
218
219
220
221 protected Element $e(String qname, String namespace, Node... nodes) {
222 return $e(qname, some(namespace), Arrays.asList(nodes));
223 }
224
225 protected Element $e(String qname, String namespace, List<Node> nodes) {
226 return $e(qname, some(namespace), nodes);
227 }
228
229 protected Node $eTxtBlank(final String name, String text) {
230 return $txtBlank(text).map(new Function<Node, Node>() {
231 @Override
232 public Node apply(Node text) {
233 final Element e = createElemDefaultNs(name);
234 e.appendChild(text);
235 return e;
236 }
237 }).getOrElse(nodeZero);
238 }
239
240 protected Node $eTxt(final String name, String text) {
241 final Element e = createElemDefaultNs(name);
242 e.appendChild($txt(text));
243 return e;
244 }
245
246 protected Node $eTxt(final String qname, final String namespace, String text) {
247 final Element e = createElemNs(namespace, qname);
248 e.appendChild($txt(text));
249 return e;
250 }
251
252 protected Element $e(String name, List<Namespace> namespaces, Node... nodes) {
253 return appendTo(appendNs(createElemDefaultNs(name), namespaces), Arrays.asList(nodes));
254 }
255
256 protected Element $e(String name, List<Namespace> namespaces, NodeList nodes) {
257 return appendTo(appendNs(createElemDefaultNs(name), namespaces), nodes);
258 }
259
260 protected Element $e(String name, List<Namespace> namespaces, List<Node> nodes) {
261 return appendTo(appendNs(createElemDefaultNs(name), namespaces), nodes);
262 }
263
264 protected Element $e(String qname, String namespace, List<Namespace> namespaces, Node... nodes) {
265 return appendTo(appendNs(createElemNs(namespace, qname), namespaces), Arrays.asList(nodes));
266 }
267
268 private Element createElemDefaultNs(String name) {
269 return createElemNs(defaultNamespace, name);
270 }
271
272 private Element createElemNs(Option<String> namespace, String qname) {
273 return createElemNs(namespace.getOrElseNull(), qname);
274 }
275
276
277
278
279
280 private Element createElemNs(String namespace, String qname) {
281 return document.createElementNS(namespace, qname);
282 }
283
284
285
286
287
288
289
290
291
292
293
294
295
296 protected Element $e(String qname, String namespace, List<Namespace> namespaces, List<Node> nodes) {
297 return appendTo(appendNs(createElemNs(namespace, qname), namespaces), nodes);
298 }
299
300
301
302
303
304 protected Node $e(String name, Option<Node>... nodes) {
305 final List<Node> existing = filter(Arrays.asList(nodes));
306 if (!existing.isEmpty()) {
307 return $e(name, existing);
308 } else {
309 return nodeZero();
310 }
311 }
312
313 protected Node $txt(String text) {
314 return document.createTextNode(text);
315 }
316
317 protected Node $cdata(String text) {
318 return document.createCDATASection(text);
319 }
320
321
322
323
324 protected Option<Node> $txtBlank(String text) {
325 return StringUtils.isNotBlank(text) ? some($txt(text)) : Option.<Node>none();
326 }
327
328
329
330
331
332 private List<Node> filter(List<Option<Node>> nodes) {
333 List<Node> result = new ArrayList<>();
334 for (Option<Node> option : nodes) {
335 option.fold(new Option.Match<Node, Void>() {
336 @Override
337 public Void some(Node node) {
338 result.add(node);
339 return null;
340 }
341
342 @Override
343 public Void none() {
344 return null;
345 }
346 });
347 }
348 return result;
349 }
350
351 private Element appendNs(Element e, List<Namespace> namespaces) {
352 for (Namespace n : namespaces) {
353 e.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + n.getPrefix(),
354 n.getNamespace());
355 }
356 return e;
357 }
358
359
360
361
362 private Element appendTo(Element e, List<Node> nodes) {
363 for (Node node : nodes)
364 appendTo(e, node);
365 return e;
366 }
367
368
369
370
371 private Element appendTo(Element e, NodeList nodes) {
372 for (int i = 0; i < nodes.getLength(); i++)
373 appendTo(e, nodes.item(i));
374 return e;
375 }
376
377
378
379
380 private void appendTo(Element e, Node n) {
381 Node toAppend = ObjectUtils.equals(n.getOwnerDocument(), document) ? n : document.importNode(n, true);
382 if (toAppend instanceof Attr) {
383 e.setAttributeNode((Attr) toAppend);
384 } else {
385 e.appendChild(toAppend);
386 }
387 }
388
389
390
391
392 protected Node nodeZero() {
393 return document.createTextNode("");
394 }
395
396
397
398
399 protected Function0<Node> nodeZero = new Function0<Node>() {
400 @Override
401 public Node apply() {
402 return nodeZero();
403 }
404 };
405
406
407
408
409 protected Function<String, Node> mkText = new Function<String, Node>() {
410 @Override
411 public Node apply(String token) {
412 return $txt(token);
413 }
414 };
415
416 protected class Namespace {
417 private final String prefix;
418 private final String namespace;
419
420 Namespace(String prefix, String namespace) {
421 this.prefix = prefix;
422 this.namespace = namespace;
423 }
424
425 public String getPrefix() {
426 return prefix;
427 }
428
429 public String getNamespace() {
430 return namespace;
431 }
432 }
433 }