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