View Javadoc

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 }