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      * @return not null
145      */
146     public static final NameMapper sieveInXmlMapper() {
147         return new NameMapper() {
148 
149             public String toElementName(String name) {
150                 boolean isControlCommand = false;
151                 for (int i=0;i< CONTROL_COMMANDS.length;i++) {
152                     if (CONTROL_COMMANDS[i].equalsIgnoreCase(name)) {
153                         isControlCommand = true;
154                         break;
155                     }
156                 }
157                 final String result;
158                 if (isControlCommand) {
159                     result = DEFAULT_NAME_CONTROL_COMMAND;
160                 } else {
161                     result = DEFAULT_NAME_ACTION_COMMAND;
162                 }
163                 return result;
164             }
165         };
166     }
167    
168     private String namespaceUri = DEFAULT_NAMESPACE;
169     private String namespacePrefix = DEFAULT_PREFIX;
170     private String stringElementName = DEFAULT_NAME_STRING;
171     private String tagElementName = DEFAULT_NAME_TAG;
172     private String numberElementName = DEFAULT_NAME_NUM;
173     private String listElementName = DEFAULT_NAME_LIST;
174     
175     private String nameAttributeName = DEFAULT_NAME_ATTRIBUTE;
176     private NameMapper commandNameMapper = sieveInXmlMapper();
177     private NameMapper testNameMapper = uniformMapper(DEFAULT_NAME_TEST);
178     
179     /**
180      * Gets mapper for command names.
181      * @return not null
182      */
183     public NameMapper getCommandNameMapper() {
184         return commandNameMapper;
185     }
186 
187     /**
188      * Sets mapper for command names.
189      * @param commandNameMapper
190      */
191     public void setCommandNameMapper(NameMapper commandNameMapper) {
192         this.commandNameMapper = commandNameMapper;
193     }
194 
195     /**
196      * Gets the element name used for lists.
197      * @return element name used for lists, not null
198      */
199     public String getListElementName() {
200         return listElementName;
201     }
202 
203     /**
204      * Sets the element name used for lists.
205      * @param listElementName not null
206      */
207     public void setListElementName(String listElementName) {
208         this.listElementName = listElementName;
209     }
210 
211     /**
212      * Gets the name of the attribute to be used to name command and tests.
213      * @return name, or null when not attribute should be written
214      */
215     public String getNameAttributeName() {
216         return nameAttributeName;
217     }
218 
219     /**
220      * Sets the name of the attribute to be used to indicate command and test names.
221      * @param nameAttributeName naming attribute, 
222      * or null when no attribute should be used
223      */
224     public void setNameAttributeName(String nameAttributeName) {
225         this.nameAttributeName = nameAttributeName;
226     }
227 
228     /**
229      * Gets the namespace prefix to be used for all elements and attributes.
230      * @return namespace prefix, or null when no namespace should be used 
231      */
232     public String getNamespacePrefix() {
233         return namespacePrefix;
234     }
235 
236     /**
237      * Sets the namespace prefix to be used for all elements and attributes.
238      * @param namespacePrefix namespace, or null when no namespace should be used
239      */
240     public void setNamespacePrefix(String namespacePrefix) {
241         this.namespacePrefix = namespacePrefix;
242     }
243 
244     /**
245      * Gets the namespace URI to be used for all elements and attributes.
246      * @return namespace URI, or null when no namespace should be used
247      */
248     public String getNamespaceUri() {
249         return namespaceUri;
250     }
251 
252     /**
253      * Sets the namespace uri to be used for all elements and attributes.
254      * @param namespaceUri namespace URI, or null when no namespace should be used
255      */
256     public void setNamespaceUri(String namespaceUri) {
257         this.namespaceUri = namespaceUri;
258     }
259 
260     /**
261      * Gets the name of the element that wraps a numeric argument.
262      * @return not null
263      */
264     public String getNumberElementName() {
265         return numberElementName;
266     }
267 
268     /**
269      * Sets the name of the element that wraps a numeric argument.
270      * @param numberElementName not null
271      */
272     public void setNumberElementName(String numberElementName) {
273         this.numberElementName = numberElementName;
274     }
275 
276     /**
277      * Gets the name of the element that wraps a string element.
278      * @return not null
279      */
280     public String getStringElementName() {
281         return stringElementName;
282     }
283 
284     /**
285      * Sets the name of the element that wraps a string element.
286      * @param stringElementName not null
287      */
288     public void setStringElementName(String stringElementName) {
289         this.stringElementName = stringElementName;
290     }
291 
292     /**
293      * Gets the name of the element that wraps a tag element.
294      * @return not null
295      */
296     public String getTagElementName() {
297         return tagElementName;
298     }
299 
300     /**
301      * Sets the name of the element that wraps a tag element
302      * @param tagElementName not null
303      */
304     public void setTagElementName(String tagElementName) {
305         this.tagElementName = tagElementName;
306     }
307 
308     /**
309      * Gets the mapper for names of test nodes.
310      * @return not null
311      */
312     public NameMapper getTestNameMapper() {
313         return testNameMapper;
314     }
315 
316     /**
317      * Sets the mapper for names of test nodes.
318      * @param testNameMapper not null
319      */
320     public void setTestNameMapper(NameMapper testNameMapper) {
321         this.testNameMapper = testNameMapper;
322     }
323 
324     /**
325      * Builds a handler to writes to the given output.
326      * @param out output, not null
327      * @return hanlder, not null
328      */
329     public SieveHandler build(final Out out) {
330         final Worker worker = new Worker(nameAttributeName, namespaceUri, namespacePrefix, stringElementName, 
331                 tagElementName, numberElementName, listElementName, commandNameMapper, testNameMapper, out);
332         return worker;
333     }
334     
335     /**
336      * Worker performs actual conversion allowing enclosing to be shared safely
337      * between threads.
338      */
339     private static final class Worker extends SieveHandler.Base {
340         
341         
342         private final String nameAttributeName;
343         
344         private final String namespaceUri;
345         private final String namespacePrefix;
346         private final String stringElementName;
347         private final String tagElementName;
348         private final String numberElementName;
349         private final String listElementName;
350         
351         private final NameMapper commandNameMapper;
352         private final NameMapper testNameMapper;
353         
354         private final Out out;
355         
356         public Worker(final String nameAttributeName, final String namespaceUri, final String namespacePrefix, 
357                 final String stringElementName, final String tagElementName, final String numberElementName, 
358                 final String listElementName, final NameMapper commandNameMapper, final NameMapper testNameMapper, 
359                 final Out out) {
360             super();
361             this.nameAttributeName = nameAttributeName;
362             this.namespaceUri = namespaceUri;
363             this.namespacePrefix = namespacePrefix;
364             this.stringElementName = stringElementName;
365             this.tagElementName = tagElementName;
366             this.numberElementName = numberElementName;
367             this.listElementName = listElementName;
368             this.commandNameMapper = commandNameMapper;
369             this.testNameMapper = testNameMapper;
370             this.out = out;
371         }
372 
373         @Override
374         public SieveHandler endCommand(String commandName) throws HaltTraversalException {
375             return closeElement();
376         }
377 
378         private SieveHandler closeElement() throws HaltTraversalException {
379             try {
380                 out.closeElement();
381                 return this;
382             } catch (IOException e) {
383                 throw new HaltTraversalException(e);
384             }
385         }
386 
387         @Override
388         public SieveHandler startCommand(String commandName) throws HaltTraversalException {
389             try {
390                 out.openElement(commandNameMapper.toElementName(commandName), namespaceUri, namespacePrefix);
391                 nameAttribute(commandName);
392                 return this;
393             } catch (IOException e) {
394                 throw new HaltTraversalException(e);
395             }
396         }
397 
398         @Override
399         public SieveHandler listMember(String string) throws HaltTraversalException {
400             try {
401                 out.openElement(stringElementName, namespaceUri, namespacePrefix);
402                 out.content(string);
403                 out.closeElement();
404                 return this;
405             } catch (IOException e) {
406                 throw new HaltTraversalException(e);
407             }
408         }
409 
410         @Override
411         public SieveHandler argument(int number) throws HaltTraversalException {
412             try {
413                 out.openElement(numberElementName, namespaceUri, namespacePrefix);
414                 out.content(Integer.toString(number));
415                 out.closeElement();
416                 return this;
417             } catch (IOException e) {
418                 throw new HaltTraversalException(e);
419             }
420         }
421 
422         @Override
423         public SieveHandler argument(String tag) throws HaltTraversalException {
424             try {
425                 out.openElement(tagElementName, namespaceUri, namespacePrefix);
426                 out.content(tag);
427                 out.closeElement();
428                 return this;
429             } catch (IOException e) {
430                 throw new HaltTraversalException(e);
431             }
432         }
433 
434         @Override
435         public SieveHandler endTest(String testName) throws HaltTraversalException {
436             return closeElement();
437         }
438 
439         @Override
440         public SieveHandler startTest(String testName) throws HaltTraversalException {
441             try {
442                 out.openElement(testNameMapper.toElementName(testName), namespaceUri, namespacePrefix);
443                 nameAttribute(testName);
444                 return this;
445             } catch (IOException e) {
446                 throw new HaltTraversalException(e);
447             }
448         }
449 
450         @Override
451         public SieveHandler endTestList() throws HaltTraversalException {
452             return closeElement();
453         }
454 
455         @Override
456         public SieveHandler startTestList() throws HaltTraversalException {
457             try {
458                 out.openElement(listElementName, namespaceUri, namespacePrefix);
459                 return this;
460             } catch (IOException e) {
461                 throw new HaltTraversalException(e);
462             }
463         }
464 
465         private void nameAttribute(String name) throws IOException {
466             if (nameAttributeName != null) {
467                 out.attribute(nameAttributeName, namespaceUri, namespacePrefix, name);
468             }
469         }
470     }
471 }