2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shale.application;
19  
20  import java.io.IOException;
21  import java.io.StreamTokenizer;
22  import java.io.StringReader;
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.regex.Pattern;
26  
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.apache.commons.chain.Command;
30  import org.apache.commons.chain.Context;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.shale.application.faces.ShaleWebContext;
34  
35  /***
36   * <p>Convenience base class for <code>Command</code> implementations that
37   * perform regular expression matching against a set of zero or more
38   * patterns.  The default <code>Command</code> implementation will perform
39   * the following algorithm.</p>
40   * <ul>
41   * <li>Retrieve the value to be compared by calling <code>value()</code>.</li>
42   * <li>If the specified value is <code>null</code>, call <code>reject()</code>
43   *     and return <code>true</code> to indicate that request procesing is
44   *     complete.</li>
45   * <li>If there are any include patterns, and the value matches one of
46   *     these patterns, call <code>accept()</code> and return
47   *     <code>false</code> to indicate request processing should continue.</li>
48   * <li>If there are any exclude patterns, and the value matches one of
49   *     these patterns, call <code>reject()</code> and return
50   *     <code>true</code> to indicate that request processing is complete.</li>
51   * <li>If there are any include patterns, and the value does not match one of
52   *     these patterns, call <code>reject()</code> and return
53   *     <code>true</code> to indicate that request processing is complete.</li>
54   * <li>Call <code>accept()</code> and return <code>false</code> to indicate
55   *     that request processing should continue.</li>
56   * </ul>
57   *
58   * <p><strong>USAGE NOTE:</strong> - See the class JavaDocs for
59   * <code>java.util.regex.Pattern</code> for the valid syntax for regular
60   * expression patterns supported by this command.</p>
61   *
62   * <p><strong>USAGE NOTE:</strong> - Commands based on this class will only
63   * be effective if used before the regular filter chain is processed.  In
64   * other words, you should invoke it as part of a <code>preprocess</code>
65   * chain in the <code>shale</code> catalog.</p>
66   *
67   * $Id: AbstractRegExpFilter.java 464373 2006-10-16 04:21:54Z rahul $
68   */
69  
70  public abstract class AbstractRegExpFilter implements Command {
71  
72  
73  
74      // -------------------------------------------------------- Static Variables
75  
76  
77      /***
78       * <p>Log instance for this class.</p>
79       */
80      private static final Log log = LogFactory.getLog(AbstractRegExpFilter.class);
81  
82  
83      // ------------------------------------------------------ Instance Variables
84  
85  
86      /***
87       * <p>Comma-delimited regular expression patterns to exclude remote host
88       * names that match.</p>
89       */
90      private String excludes = null;
91  
92  
93      /***
94       * <p>Array of regular expression patterns for the excludes list.</p>
95       */
96      private Pattern excludesPatterns[] = new Pattern[0];
97  
98  
99      /***
100      * <p>Comma-delimited regular expression patterns to include remote host
101      * names that match.</p>
102      */
103     private String includes = null;
104 
105 
106     /***
107      * <p>Array of regular expression patterns for the includes list.</p>
108      */
109     private Pattern includesPatterns[] = new Pattern[0];
110 
111     /***
112      * <p>Returns an array of regular expression patterns for the includes list.</p>
113      */
114     protected Pattern[] getIncludesPatterns() { return includesPatterns; }
115 
116 
117     // -------------------------------------------------------------- Properties
118 
119 
120     /***
121      * <p>Return the comma-delimited regular expresson patterns to exclude
122      * remote host names that match, if any; otherwise, return
123      * <code>null</code>.</p>
124      */
125     public String getExcludes() { return this.excludes; }
126 
127 
128     /***
129      * <p>Set the comma-delimited regular expression patterns to exclude
130      * remote host names that match, if any; or <code>null</code> for no
131      * restrictions.</p>
132      *
133      * @param excludes New exclude pattern(s)
134      */
135     public void setExcludes(String excludes) {
136         this.excludes = excludes;
137         this.excludesPatterns = precompile(excludes);
138     }
139 
140 
141     /***
142      * <p>Return the comma-delimited regular expresson patterns to include
143      * remote host names that match, if any; otherwise, return
144      * <code>null</code>.</p>
145      */
146     public String getIncludes() { return this.includes; }
147 
148 
149     /***
150      * <p>Set the comma-delimited regular expression patterns to include
151      * remote host names that match, if any; or <code>null</code> for no
152      * restrictions.</p>
153      *
154      * @param includes New include pattern(s)
155      */
156     public void setIncludes(String includes) {
157         this.includes = includes;
158         this.includesPatterns = precompile(includes);
159     }
160 
161 
162     // --------------------------------------------------------- Command Methods
163 
164 
165     /***
166      * <p>Perform the matching algorithm described in our class Javadocs
167      * against the value returned by the <code>value()</code> method.</p>
168      *
169      * @param context <code>ShaleWebContext</code> for this request
170      *
171      * @exception Exception if an error occurs
172      */
173     public boolean execute(Context context) throws Exception {
174 
175         // Acquire the value to be tested
176         ShaleWebContext webContext = (ShaleWebContext) context;
177         String value = value(webContext);
178         if (log.isDebugEnabled()) {
179             log.debug("execute(" + value + ")");
180         }
181         if (value == null) {
182             if (log.isTraceEnabled()) {
183                 log.trace("  reject(null)");
184             }
185             reject(webContext);
186             return true;
187         }
188 
189         // Check for a match on the included list
190         if (matches(value, includesPatterns, true)) {
191             if (log.isTraceEnabled()) {
192                 log.trace("  accept(include)");
193             }
194             accept(webContext);
195             return false;
196         }
197 
198         // Check for a match on the excluded list
199         if (matches(value, excludesPatterns, false)) {
200             if (log.isTraceEnabled()) {
201                 log.trace("  reject(exclude)");
202             }
203             reject(webContext);
204             return true;
205         }
206 
207         // If there was at least one include pattern,
208         // unconditionally reject this request
209         if ((includesPatterns != null) && (includesPatterns.length > 0)) {
210             if (log.isTraceEnabled()) {
211                 log.trace("  reject(not include)");
212             }
213             reject(webContext);
214             return true;
215         }
216 
217         // Unconditionally accept this request
218         if (log.isTraceEnabled()) {
219             log.trace("  accept(default)");
220         }
221         accept(webContext);
222         return false;
223 
224     }
225 
226 
227     // ------------------------------------------------------- Protected Methods
228 
229 
230     /***
231      * <p>Perform whatever processing is necessary to mark this request as
232      * being accepted.  The default implementation does nothing.</p>
233      *
234      * @param context <code>Context</code> for the current request
235      *
236      * @exception Exception if an error occurs
237      */
238     protected void accept(ShaleWebContext context) throws Exception {
239 
240         // No action required
241 
242     }
243 
244 
245     /***
246      * <p>Perform whatever processing is necessary to mark this request as
247      * being rejected.  The default implementation returns a status code
248      * of <code>HttpServletResponse.SC_FORBIDDEN</code>.</p>
249      *
250      * @param context <code>Context</code> for the current request
251      *
252      * @exception Exception if an error occurs
253      */
254     protected void reject(ShaleWebContext context) throws Exception {
255 
256         HttpServletResponse response = context.getResponse();
257         response.sendError(HttpServletResponse.SC_FORBIDDEN);
258 
259     }
260 
261 
262     /***
263      * <p>Return the value, from the specified context, that should be used
264      * to match against the configured exclude and include patterns.  This
265      * method must be implemented by a concrete subclass.</p>
266      *
267      * @param context <code>Context</code> for the current request
268      */
269     protected abstract String value(ShaleWebContext context);
270 
271 
272     // --------------------------------------------------------- Private Methods
273 
274 
275     /***
276      * <p>Match the specified expression against the specified precompiled
277      * patterns.  If there are no patterns, return the specified unrestricted
278      * return value; otherwise, return <code>true</code> if the expression
279      * matches one of the patterns, or <code>false</code> otherwise.</p>
280      *
281      * @param expr Expression to be tested
282      * @param patterns Array of <code>Pattern</code> to be tested against
283      * @param unrestricted Result to be returned if there are no matches
284      */
285     protected boolean matches(String expr, Pattern patterns[],
286                             boolean unrestricted) {
287 
288         // Check for the unrestricted case
289         if ((patterns == null) || (patterns.length == 0)) {
290             return unrestricted;
291         }
292 
293         // Compare each pattern in turn for a match
294         for (int i = 0; i < patterns.length; i++) {
295             if (patterns[i].matcher(expr).matches()) {
296                 return true;
297             }
298         }
299 
300         // No match found, so return false
301         return false;
302 
303     }
304 
305 
306     /***
307      * <p>Parse the specified string of comma-delimited (and optionally quoted,
308      * if an embedded comma is required) regular expressions into an array
309      * of precompiled <code>Pattern</code> instances that represent these
310      * expressons.</p>
311      *
312      * @param expr Comma-delimited regular expressions
313      */
314      private Pattern[] precompile(String expr) {
315 
316         if (expr == null) {
317             return new Pattern[0];
318         }
319 
320         // Set up to parse the specified expression
321         StreamTokenizer st =
322           new StreamTokenizer(new StringReader(expr));
323         st.eolIsSignificant(false);
324         st.lowerCaseMode(false);
325         st.slashSlashComments(false);
326         st.slashStarComments(false);
327         st.wordChars(0x00, 0xff);
328         st.quoteChar('\'');
329         st.quoteChar('"');
330         st.whitespaceChars(0, ' ');
331         st.whitespaceChars(',', ',');
332         List list = new ArrayList();
333         int type = 0;
334 
335         // Parse each included expression
336         while (true) {
337             try {
338                 type = st.nextToken();
339             } catch (IOException e) {
340                 ; // Can not happen
341             }
342             if (type == StreamTokenizer.TT_EOF) {
343                 break;
344             } else if (type == StreamTokenizer.TT_NUMBER) {
345                 list.add(Pattern.compile("" + st.nval));
346             } else if (type == StreamTokenizer.TT_WORD) {
347                 list.add(Pattern.compile(st.sval.trim()));
348             } else {
349                 throw new IllegalArgumentException(expr);
350             }
351         }
352 
353         // Return the precompiled patterns as an array
354         return (Pattern[]) list.toArray(new Pattern[list.size()]);
355 
356     }
357 
358 
359 }