1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19 package org.apache.jsieve.util;
20
21 import java.io.IOException;
22
23 /**
24 * <p>Converts Sieve nodes to xml.
25 * Settings default to <code>draft-freed-sieve-in-xml-01</code>
26 * <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
27 * Sieve Email Filtering: Sieves and display directives in XML</blockquote>.
28 * </p>
29 * <h4>Known limitations</h4>
30 * <p>For simplicity, allow elements are out into a single namespace.</p>
31 */
32 public class SieveToXml {
33
34 public static final String DEFAULT_NAME_ATTRIBUTE = "name";
35
36 public static final String DEFAULT_NAME_ACTION_COMMAND = "action";
37
38 public static final String DEFAULT_NAME_CONTROL_COMMAND = "control";
39
40 public static final String DEFAULT_NAME_TEST = "test";
41
42 public static final String DEFAULT_NAME_LIST = "list";
43
44 public static final String DEFAULT_NAME_NUM = "num";
45
46 public static final String DEFAULT_NAME_TAG = "tag";
47
48 public static final String DEFAULT_NAME_STRING = "str";
49
50 public static final String DEFAULT_PREFIX = "sieve";
51
52 public static final String DEFAULT_NAMESPACE = "urn:ietf:params:xml:ns:sieve";
53
54 /** Control commands (as listed in RFC 3028) */
55 public static final String[] CONTROL_COMMANDS = {"If", "Require", "Stop"};
56
57 /**
58 * <p>Simple infoset output.
59 * </p><p>
60 * Note that the approach taken to namespaces is slightly different from both
61 * <a href='http://java.sun.com/j2ee/1.4/docs/api/javax/xml/namespace/QName.html'>QName</a>
62 * and <a href='http://www.saxproject.org'>SAX</a>.
63 * </p>
64 * <ul>
65 * <li>
66 * The <code>localPart</code> parameter gives the
67 * <a href='http://www.w3.org/TR/REC-xml-names/#NT-LocalPart' rel='tag'>LocalPart</a>
68 * and <code>MUST</code> be provided in all cases.
69 * When the name is an <a href='http://www.w3.org/TR/REC-xml-names/#NT-UnprefixedName' rel='tag'>UnprefixedName</a>,
70 * this is the complete <a href='http://www.w3.org/TR/REC-xml-names/#dt-qualname' rel='tag'>QName</a>.
71 * When the name is a <a href='http://www.w3.org/TR/REC-xml-names/#NT-PrefixedName' rel='tag'>PrefixedName</a>,
72 * the output must combine this with the <a href='http://www.w3.org/TR/REC-xml-names/#NT-Prefix' rel='tag'>Prefix</a>
73 * </li>
74 * <li>
75 * </li>
76 * </ul>
77 * </p>
78 */
79 public interface Out {
80
81 /**
82 * Starts an XML element.
83 * @throws IOException when output fails
84 */
85 public void openElement(CharSequence localName, CharSequence uri, CharSequence prefix) throws IOException;
86
87 /**
88 * Outputs a attribute.
89 * @param value unescaped XML attribute content, not null
90 * @throws IOException when output fails
91 */
92 public void attribute(CharSequence localName, CharSequence uri, CharSequence prefix, CharSequence value) throws IOException;
93
94 /**
95 * Outputs body text.
96 * All attribute will be output before any body text
97 * so this call implicitly marks the end of any attributes
98 * for the element.
99 * @param text unescaped body text, not null
100 * @throws IOException when output fails
101 */
102 public void content(CharSequence text) throws IOException;
103
104 /**
105 * Ends an XML Element.
106 * @throws IOException when output fails
107 */
108 public void closeElement() throws IOException;
109 }
110
111 /**
112 * Maps node names to element names.
113 */
114 public interface NameMapper {
115 /**
116 * Converts the given node name to an
117 * element local name.
118 * @param name not null
119 * @return element local name to use for given name, not null
120 */
121 public String toElementName(String name);
122
123 }
124
125 /**
126 * Creates a mapper which will return the same
127 * name for any node.
128 * @param elementLocalName to be returned for all names, not null
129 * @return not null
130 */
131 public static final NameMapper uniformMapper(final String elementLocalName) {
132 return new NameMapper() {
133 public String toElementName(String name) {
134 return elementLocalName;
135 }
136 };
137 }
138
139 /**
140 * Creates a mapper which returns values given in
141 * <code>draft-freed-sieve-in-xml-01</code>
142 * <blockquote cite='http://tools.ietf.org/id/draft-freed-sieve-in-xml-01.txt'>
143 * Sieve Email Filtering: Sieves and display directives in XML</blockquote>.
144 * @param elementName
145 * @return
146 */
147 public static final NameMapper sieveInXmlMapper() {
148 return new NameMapper() {
149
150 public String toElementName(String name) {
151 boolean isControlCommand = false;
152 for (int i=0;i< CONTROL_COMMANDS.length;i++) {
153 if (CONTROL_COMMANDS[i].equalsIgnoreCase(name)) {
154 isControlCommand = true;
155 break;
156 }
157 }
158 final String result;
159 if (isControlCommand) {
160 result = DEFAULT_NAME_CONTROL_COMMAND;
161 } else {
162 result = DEFAULT_NAME_ACTION_COMMAND;
163 }
164 return result;
165 }
166 };
167 }
168
169 private String namespaceUri = DEFAULT_NAMESPACE;
170 private String namespacePrefix = DEFAULT_PREFIX;
171 private String stringElementName = DEFAULT_NAME_STRING;
172 private String tagElementName = DEFAULT_NAME_TAG;
173 private String numberElementName = DEFAULT_NAME_NUM;
174 private String listElementName = DEFAULT_NAME_LIST;
175
176 private String nameAttributeName = DEFAULT_NAME_ATTRIBUTE;
177 private NameMapper commandNameMapper = sieveInXmlMapper();
178 private NameMapper testNameMapper = uniformMapper(DEFAULT_NAME_TEST);
179
180 /**
181 * Gets mapper for command names.
182 * @return not null
183 */
184 public NameMapper getCommandNameMapper() {
185 return commandNameMapper;
186 }
187
188 /**
189 * Sets mapper for command names.
190 * @param commandNameMapper
191 */
192 public void setCommandNameMapper(NameMapper commandNameMapper) {
193 this.commandNameMapper = commandNameMapper;
194 }
195
196 /**
197 * Gets the element name used for lists.
198 * @return element name used for lists, not null
199 */
200 public String getListElementName() {
201 return listElementName;
202 }
203
204 /**
205 * Sets the element name used for lists.
206 * @param listElementName not null
207 */
208 public void setListElementName(String listElementName) {
209 this.listElementName = listElementName;
210 }
211
212 /**
213 * Gets the name of the attribute to be used to name command and tests.
214 * @return name, or null when not attribute should be written
215 */
216 public String getNameAttributeName() {
217 return nameAttributeName;
218 }
219
220 /**
221 * Sets the name of the attribute to be used to indicate command and test names.
222 * @param nameAttributeName naming attribute,
223 * or null when no attribute should be used
224 */
225 public void setNameAttributeName(String nameAttributeName) {
226 this.nameAttributeName = nameAttributeName;
227 }
228
229 /**
230 * Gets the namespace prefix to be used for all elements and attributes.
231 * @return namespace prefix, or null when no namespace should be used
232 */
233 public String getNamespacePrefix() {
234 return namespacePrefix;
235 }
236
237 /**
238 * Sets the namespace prefix to be used for all elements and attributes.
239 * @param namespacePrefix namespace, or null when no namespace should be used
240 */
241 public void setNamespacePrefix(String namespacePrefix) {
242 this.namespacePrefix = namespacePrefix;
243 }
244
245 /**
246 * Gets the namespace URI to be used for all elements and attributes.
247 * @return namespace URI, or null when no namespace should be used
248 */
249 public String getNamespaceUri() {
250 return namespaceUri;
251 }
252
253 /**
254 * Sets the namespace uri to be used for all elements and attributes.
255 * @param namespaceUri namespace URI, or null when no namespace should be used
256 */
257 public void setNamespaceUri(String namespaceUri) {
258 this.namespaceUri = namespaceUri;
259 }
260
261 /**
262 * Gets the name of the element that wraps a numeric argument.
263 * @return not null
264 */
265 public String getNumberElementName() {
266 return numberElementName;
267 }
268
269 /**
270 * Sets the name of the element that wraps a numeric argument.
271 * @param numberElementName not null
272 */
273 public void setNumberElementName(String numberElementName) {
274 this.numberElementName = numberElementName;
275 }
276
277 /**
278 * Gets the name of the element that wraps a string element.
279 * @return not null
280 */
281 public String getStringElementName() {
282 return stringElementName;
283 }
284
285 /**
286 * Sets the name of the element that wraps a string element.
287 * @param stringElementName not null
288 */
289 public void setStringElementName(String stringElementName) {
290 this.stringElementName = stringElementName;
291 }
292
293 /**
294 * Gets the name of the element that wraps a tag element.
295 * @return not null
296 */
297 public String getTagElementName() {
298 return tagElementName;
299 }
300
301 /**
302 * Sets the name of the element that wraps a tag element
303 * @param tagElementName not null
304 */
305 public void setTagElementName(String tagElementName) {
306 this.tagElementName = tagElementName;
307 }
308
309 /**
310 * Gets the mapper for names of test nodes.
311 * @return not null
312 */
313 public NameMapper getTestNameMapper() {
314 return testNameMapper;
315 }
316
317 /**
318 * Sets the mapper for names of test nodes.
319 * @param testNameMapper not null
320 */
321 public void setTestNameMapper(NameMapper testNameMapper) {
322 this.testNameMapper = testNameMapper;
323 }
324
325 /**
326 * Builds a handler to writes to the given output.
327 * @param out output, not null
328 * @return hanlder, not null
329 */
330 public SieveHandler build(final Out out) {
331 final Worker worker = new Worker(nameAttributeName, namespaceUri, namespacePrefix, stringElementName,
332 tagElementName, numberElementName, listElementName, commandNameMapper, testNameMapper, out);
333 return worker;
334 }
335
336 /**
337 * Worker performs actual conversion allowing enclosing to be shared safely
338 * between threads.
339 */
340 private static final class Worker extends SieveHandler.Base {
341
342
343 private final String nameAttributeName;
344
345 private final String namespaceUri;
346 private final String namespacePrefix;
347 private final String stringElementName;
348 private final String tagElementName;
349 private final String numberElementName;
350 private final String listElementName;
351
352 private final NameMapper commandNameMapper;
353 private final NameMapper testNameMapper;
354
355 private final Out out;
356
357 public Worker(final String nameAttributeName, final String namespaceUri, final String namespacePrefix,
358 final String stringElementName, final String tagElementName, final String numberElementName,
359 final String listElementName, final NameMapper commandNameMapper, final NameMapper testNameMapper,
360 final Out out) {
361 super();
362 this.nameAttributeName = nameAttributeName;
363 this.namespaceUri = namespaceUri;
364 this.namespacePrefix = namespacePrefix;
365 this.stringElementName = stringElementName;
366 this.tagElementName = tagElementName;
367 this.numberElementName = numberElementName;
368 this.listElementName = listElementName;
369 this.commandNameMapper = commandNameMapper;
370 this.testNameMapper = testNameMapper;
371 this.out = out;
372 }
373
374 //@Override
375 public SieveHandler endCommand(String commandName) throws HaltTraversalException {
376 return closeElement();
377 }
378
379 private SieveHandler closeElement() throws HaltTraversalException {
380 try {
381 out.closeElement();
382 return this;
383 } catch (IOException e) {
384 throw new HaltTraversalException(e);
385 }
386 }
387
388 //@Override
389 public SieveHandler startCommand(String commandName) throws HaltTraversalException {
390 try {
391 out.openElement(commandNameMapper.toElementName(commandName), namespaceUri, namespacePrefix);
392 nameAttribute(commandName);
393 return this;
394 } catch (IOException e) {
395 throw new HaltTraversalException(e);
396 }
397 }
398
399 //@Override
400 public SieveHandler listMember(String string) throws HaltTraversalException {
401 try {
402 out.openElement(stringElementName, namespaceUri, namespacePrefix);
403 out.content(string);
404 out.closeElement();
405 return this;
406 } catch (IOException e) {
407 throw new HaltTraversalException(e);
408 }
409 }
410
411 //@Override
412 public SieveHandler argument(int number) throws HaltTraversalException {
413 try {
414 out.openElement(numberElementName, namespaceUri, namespacePrefix);
415 out.content(Integer.toString(number));
416 out.closeElement();
417 return this;
418 } catch (IOException e) {
419 throw new HaltTraversalException(e);
420 }
421 }
422
423 //@Override
424 public SieveHandler argument(String tag) throws HaltTraversalException {
425 try {
426 out.openElement(tagElementName, namespaceUri, namespacePrefix);
427 out.content(tag);
428 out.closeElement();
429 return this;
430 } catch (IOException e) {
431 throw new HaltTraversalException(e);
432 }
433 }
434
435 //@Override
436 public SieveHandler endTest(String testName) throws HaltTraversalException {
437 return closeElement();
438 }
439
440 //@Override
441 public SieveHandler startTest(String testName) throws HaltTraversalException {
442 try {
443 out.openElement(testNameMapper.toElementName(testName), namespaceUri, namespacePrefix);
444 nameAttribute(testName);
445 return this;
446 } catch (IOException e) {
447 throw new HaltTraversalException(e);
448 }
449 }
450
451 //@Override
452 public SieveHandler endTestList() throws HaltTraversalException {
453 return closeElement();
454 }
455
456 //@Override
457 public SieveHandler startTestList() throws HaltTraversalException {
458 try {
459 out.openElement(listElementName, namespaceUri, namespacePrefix);
460 return this;
461 } catch (IOException e) {
462 throw new HaltTraversalException(e);
463 }
464 }
465
466 private void nameAttribute(String name) throws IOException {
467 if (nameAttributeName != null) {
468 out.attribute(nameAttributeName, namespaceUri, namespacePrefix, name);
469 }
470 }
471 }
472 }