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  
20  package org.apache.jsieve;
21  
22  import java.io.InputStream;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.jsieve.exception.SieveException;
26  import org.apache.jsieve.exception.StopException;
27  import org.apache.jsieve.mail.ActionKeep;
28  import org.apache.jsieve.mail.MailAdapter;
29  import org.apache.jsieve.parser.generated.Node;
30  import org.apache.jsieve.parser.generated.ParseException;
31  import org.apache.jsieve.parser.generated.SieveParser;
32  import org.apache.jsieve.parser.generated.SieveParserVisitor;
33  import org.apache.jsieve.parser.generated.SimpleNode;
34  
35  /**
36   * <p>
37   * SieveFactory is the primary invocation point for all Sieve
38   * operations. These are...
39   * <dl>
40   * <dt>{@link #parse(InputStream)}</dt>
41   * <dd> Parse a Sieve script into a hierarchy of parsed nodes. A succesful parse
42   * means the script is lexically and gramatically valid according to RFC 3028,
43   * section 8. The result is the start node of the parsed Sieve script. The start
44   * node is resuable. Typically it is stored for reuse in all subsequent
45   * evaluations of the script. </dd>
46   * <dt>{@link #evaluate(MailAdapter, Node)}</dt>
47   * <dd> Evaluate an RFC 822 compliant mail message wrapped in a {@link MailAdapter}
48   * against the parse result referenced by the start node from the Parse
49   * operation above. As evaluation proceeds a List of {@link org.apache.jsieve.mail.Action}s 
50   * is added to the MailAdapter. At the end of evaluation, each Action in the List is executed in
51   * the order they were added. </dd>
52   * <dt>{@link #interpret(MailAdapter, InputStream)}</dt>
53   * <dd>A concatenation of parse and evaluate. Useful for testing, but generally
54   * the parse result should be stored for reuse in subsequent evaluations. </dd>
55   * </dl>
56   * </p>
57   * <h4>Thread Safety</h4>
58   * <p>
59   * An instance can be safely accessed concurrently by multiple threads
60   * provided that the managers used to construct the instance 
61   * (when {@link #SieveFactory(CommandManager, ComparatorManager, TestManager, Log)} 
62   * is called) are thread safe.
63   * </p>
64   */
65  public class SieveFactory {
66  
67      private final CommandManager commandManager;
68  
69      private final ComparatorManager comparatorManager;
70  
71      private final TestManager testManager;
72  
73      private final Log log;
74  
75      /**
76       * Constructor for SieveFactory.
77       */
78      public SieveFactory(final CommandManager commandManager,
79              final ComparatorManager comparatorManager,
80              final TestManager testManager, final Log log) {
81          super();
82          this.commandManager = commandManager;
83          this.comparatorManager = comparatorManager;
84          this.testManager = testManager;
85          this.log = log;
86      }
87  
88      /**
89       * Method parse parses a Sieve script into a hierarchy of parsed nodes. A
90       * successful parse means the script is lexically and grammatically valid
91       * according to RFC 3028, section 8. The result is the start node of the
92       * parsed Sieve script. The start node is reusable. Typically it is stored
93       * for reuse in subsequent evaluations of the script.
94       * 
95       * @param inputStream
96       * @return Node
97       * @throws ParseException
98       */
99      public Node parse(InputStream inputStream) throws ParseException {
100         try {
101             final SimpleNode node = new SieveParser(inputStream, "UTF-8")
102                     .start();
103             SieveValidationVisitor visitor = new SieveValidationVisitor(
104                     commandManager, testManager, comparatorManager);
105             node.jjtAccept(visitor, null);
106             return node;
107         } catch (ParseException ex) {
108             if (log.isErrorEnabled())
109                 log.error("Parse failed. Reason: " + ex.getMessage());
110             if (log.isDebugEnabled())
111                 log.debug("Parse failed.", ex);
112             throw ex;
113         } catch (SieveException ex) {
114             if (log.isErrorEnabled())
115                 log.error("Parse failed. Reason: " + ex.getMessage());
116             if (log.isDebugEnabled())
117                 log.debug("Parse failed.", ex);
118             throw new ParseException(ex.getMessage());
119         }
120     }
121 
122     /**
123      * <p>
124      * Method evaluate evaluates an RFC 822 compliant mail message wrapped in a
125      * MailAdapter by visting each node of the parsed script beginning at the
126      * passed start node. As evaluation proceeds a List of Actions is added to
127      * the MailAdapter.
128      * <p>
129      * 
130      * <p>
131      * At the start of evaluation an 'implicitKeep' state is set. This can be
132      * cancelled by a Command during evaluation. If 'implicitKeep' is still set
133      * at the end of evaluation, a Keep Action is added to the List of Actions.
134      * Finally, each Action in the List is executed in the order they were
135      * added.
136      * </p>
137      * 
138      * @param mail
139      * @param startNode
140      * @throws SieveException
141      */
142     public void evaluate(MailAdapter mail, Node startNode)
143             throws SieveException {
144         final SieveContext context = new BaseSieveContext(commandManager,
145                 comparatorManager, testManager, log);
146         try {
147             // Ensure that the context is set on the mail
148             mail.setContext(context);
149             
150             SieveParserVisitor visitor = new SieveParserVisitorImpl(context);
151             try {
152                 // Evaluate the Nodes
153                 startNode.jjtAccept(visitor, mail);
154     
155             } catch (StopException ex) {
156                 // Stop is OK
157             } catch (SieveException ex) {
158                 if (log.isErrorEnabled())
159                     log.error("Evaluation failed. Reason: " + ex.getMessage());
160                 if (log.isDebugEnabled())
161                     log.debug("Evaluation failed.", ex);
162                 throw ex;
163             }
164     
165             // If after evaluating all of the nodes or stopping, implicitKeep is
166             // still
167             // in effect, add a Keep to the list of Actions.
168             if (context.getCommandStateManager().isImplicitKeep())
169                 mail.addAction(new ActionKeep());
170     
171             // Execute the List of Actions
172             try {
173                 mail.executeActions();
174             } catch (SieveException ex) {
175                 if (log.isErrorEnabled())
176                     log.error("Evaluation failed. Reason: " + ex.getMessage());
177                 if (log.isDebugEnabled())
178                     log.debug("Evaluation failed.", ex);
179                 throw ex;
180             }
181         } finally {
182             // Tidy up by ensuring that a reference to the context is not held by the adapter.
183             // This prevents leaks when the adapter stores the context in a thread local variable.
184             mail.setContext(null);
185         }
186     }
187 
188     /**
189      * Method interpret parses a Sieve script and then evaluates the result
190      * against a mail.
191      * 
192      * @param mail
193      * @param inputStream
194      * @throws ParseException
195      * @throws SieveException
196      */
197     public void interpret(MailAdapter mail, InputStream inputStream)
198             throws ParseException, SieveException {
199         evaluate(mail, parse(inputStream));
200     }
201 }