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 }