View Javadoc

1   package org.apache.turbine.util.parser;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.net.URLDecoder;
23  
24  import java.util.Enumeration;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.StringTokenizer;
30  
31  import javax.servlet.http.HttpServletRequest;
32  
33  import org.apache.commons.collections.set.CompositeSet;
34  import org.apache.commons.fileupload.FileItem;
35  import org.apache.commons.lang.ArrayUtils;
36  import org.apache.commons.logging.Log;
37  import org.apache.commons.logging.LogFactory;
38  
39  import org.apache.turbine.TurbineConstants;
40  import org.apache.turbine.services.upload.TurbineUpload;
41  import org.apache.turbine.services.upload.UploadService;
42  import org.apache.turbine.util.TurbineException;
43  import org.apache.turbine.util.pool.Recyclable;
44  
45  /***
46   * DefaultParameterParser is a utility object to handle parsing and
47   * retrieving the data passed via the GET/POST/PATH_INFO arguments.
48   *
49   * <p>NOTE: The name= portion of a name=value pair may be converted
50   * to lowercase or uppercase when the object is initialized and when
51   * new data is added.  This behaviour is determined by the url.case.folding
52   * property in TurbineResources.properties.  Adding a name/value pair may
53   * overwrite existing name=value pairs if the names match:
54   *
55   * <pre>
56   * ParameterParser pp = data.getParameters();
57   * pp.add("ERROR",1);
58   * pp.add("eRrOr",2);
59   * int result = pp.getInt("ERROR");
60   * </pre>
61   *
62   * In the above example, result is 2.
63   *
64   * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
65   * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
66   * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
67   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
68   * @version $Id: DefaultParameterParser.java 534527 2007-05-02 16:10:59Z tv $
69   */
70  public class DefaultParameterParser
71      extends BaseValueParser
72      implements ParameterParser, Recyclable
73  {
74      /*** Logging */
75      private static Log log = LogFactory.getLog(DefaultParameterParser.class);
76  
77      /*** The servlet request to parse. */
78      private HttpServletRequest request = null;
79  
80      /*** The raw data of a file upload. */
81      private byte[] uploadData = null;
82  
83      /*** Map of request parameters to FileItem[]'s */
84      private Map fileParameters = new HashMap();
85  
86      /*** Turbine Upload Service reference */
87      private static UploadService uploadService = null;
88  
89      /*** Do we have an upload Service? */
90      private static boolean uploadServiceIsAvailable = false;
91  
92      /***
93       * Create a new empty instance of ParameterParser.  Uses the
94       * default character encoding (US-ASCII).
95       *
96       * <p>To add name/value pairs to this set of parameters, use the
97       * <code>add()</code> methods.
98       */
99      public DefaultParameterParser()
100     {
101         super();
102         configureUploadService();
103     }
104 
105     /***
106      * Create a new empty instance of ParameterParser. Takes a
107      * character encoding name to use when converting strings to
108      * bytes.
109      *
110      * <p>To add name/value pairs to this set of parameters, use the
111      * <code>add()</code> methods.
112      *
113      * @param characterEncoding The character encoding of strings.
114      */
115     public DefaultParameterParser(String characterEncoding)
116     {
117         super(characterEncoding);
118         configureUploadService();
119     }
120 
121     /***
122      * Checks for availability of the Upload Service. We do this
123      * check only once at Startup, because the getService() call
124      * is really expensive and we don't have to run it every time
125      * we process a request.
126      */
127     private void configureUploadService()
128     {
129         uploadServiceIsAvailable = TurbineUpload.isAvailable();
130         if (uploadServiceIsAvailable)
131         {
132             uploadService = TurbineUpload.getService();
133         }
134     }
135 
136     /***
137      * Disposes the parser.
138      */
139     public void dispose()
140     {
141         this.request = null;
142         this.uploadData = null;
143         this.fileParameters.clear();
144         super.dispose();
145     }
146 
147     /***
148      * Gets the parsed servlet request.
149      *
150      * @return the parsed servlet request or null.
151      */
152     public HttpServletRequest getRequest()
153     {
154         return request;
155     }
156 
157     /***
158      * Sets the servlet request to the parser.  This requires a
159      * valid HttpServletRequest object.  It will attempt to parse out
160      * the GET/POST/PATH_INFO data and store the data into a Map.
161      * There are convenience methods for retrieving the data as a
162      * number of different datatypes.  The PATH_INFO data must be a
163      * URLEncoded() string.
164      * <p>
165      * To add name/value pairs to this set of parameters, use the
166      * <code>add()</code> methods.
167      *
168      * @param request An HttpServletRequest.
169      */
170     public void setRequest(HttpServletRequest request)
171     {
172         clear();
173 
174         uploadData = null;
175 
176         String enc = request.getCharacterEncoding();
177         setCharacterEncoding(enc != null
178                 ? enc
179                 : TurbineConstants.PARAMETER_ENCODING_DEFAULT);
180 
181         String contentType = request.getHeader("Content-type");
182 
183         if (uploadServiceIsAvailable
184                 && uploadService.getAutomatic()
185                 && contentType != null
186                 && contentType.startsWith("multipart/form-data"))
187         {
188             if (log.isDebugEnabled())
189             {
190                 log.debug("Running the Turbine Upload Service");
191             }
192 
193             try
194             {
195                 TurbineUpload.parseRequest(request, this);
196             }
197             catch (TurbineException e)
198             {
199                 log.error("File upload failed", e);
200             }
201         }
202 
203         for (Enumeration names = request.getParameterNames();
204              names.hasMoreElements();)
205         {
206             String paramName = (String) names.nextElement();
207             add(paramName,
208                     request.getParameterValues(paramName));
209         }
210 
211         // Also cache any pathinfo variables that are passed around as
212         // if they are query string data.
213         try
214         {
215             boolean isNameTok = true;
216             String paramName = null;
217             String paramValue = null;
218 
219             for ( StringTokenizer st =
220                           new StringTokenizer(request.getPathInfo(), "/");
221                   st.hasMoreTokens();)
222             {
223                 if (isNameTok)
224                 {
225                     paramName = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
226                     isNameTok = false;
227                 }
228                 else
229                 {
230                     paramValue = URLDecoder.decode(st.nextToken(), getCharacterEncoding());
231                     if (paramName.length() > 0)
232                     {
233                         add(paramName, paramValue);
234                     }
235                     isNameTok = true;
236                 }
237             }
238         }
239         catch (Exception e)
240         {
241             // If anything goes wrong above, don't worry about it.
242             // Chances are that the path info was wrong anyways and
243             // things that depend on it being right will fail later
244             // and should be caught later.
245         }
246 
247         this.request = request;
248 
249         if (log.isDebugEnabled())
250         {
251             log.debug("Parameters found in the Request:");
252             for (Iterator it = keySet().iterator(); it.hasNext();)
253             {
254                 String key = (String) it.next();
255                 log.debug("Key: " + key + " -> " + getString(key));
256             }
257         }
258     }
259 
260     /***
261      * Sets the uploadData byte[]
262      *
263      * @param uploadData A byte[] with data.
264      */
265     public void setUploadData(byte[] uploadData)
266     {
267         this.uploadData = uploadData;
268     }
269 
270     /***
271      * Gets the uploadData byte[]
272      *
273      * @return uploadData A byte[] with data.
274      */
275     public byte[] getUploadData()
276     {
277         return uploadData;
278     }
279 
280     /***
281      * Add a FileItem object as a parameters.  If there are any
282      * FileItems already associated with the name, append to the
283      * array.  The reason for this is that RFC 1867 allows multiple
284      * files to be associated with single HTML input element.
285      *
286      * @param name A String with the name.
287      * @param value A FileItem with the value.
288      * @deprecated Use add(String name, FileItem item)
289      */
290     public void append(String name, FileItem item)
291     {
292         add(name, item);
293     }
294 
295     /***
296      * Add a FileItem object as a parameters.  If there are any
297      * FileItems already associated with the name, append to the
298      * array.  The reason for this is that RFC 1867 allows multiple
299      * files to be associated with single HTML input element.
300      *
301      * @param name A String with the name.
302      * @param value A FileItem with the value.
303      */
304     public void add(String name, FileItem item)
305     {
306         FileItem[] items = getFileItemParam(name);
307         items = (FileItem []) ArrayUtils.add(items, item);
308         putFileItemParam(name, items);
309     }
310 
311     /***
312      * Gets the set of keys (FileItems and regular parameters)
313      *
314      * @return A <code>Set</code> of the keys.
315      */
316     public Set keySet()
317     {
318         return new CompositeSet(new Set[] { super.keySet(), fileParameters.keySet() } );
319     }
320 
321     /***
322      * Determine whether a given key has been inserted.  All keys are
323      * stored in lowercase strings, so override method to account for
324      * this.
325      *
326      * @param key An Object with the key to search for.
327      * @return True if the object is found.
328      */
329     public boolean containsKey(Object key)
330     {
331         if (super.containsKey(key))
332         {
333             return true;
334         }
335 
336         return fileParameters.containsKey(convert(String.valueOf(key)));
337     }
338 
339 
340     /***
341      * Return a FileItem object for the given name.  If the name does
342      * not exist or the object stored is not a FileItem, return null.
343      *
344      * @param name A String with the name.
345      * @return A FileItem.
346      */
347     public FileItem getFileItem(String name)
348     {
349         FileItem [] value = getFileItemParam(name);
350 
351         return (value == null
352                 || value.length == 0)
353                 ? null : value[0];
354     }
355 
356     /***
357      * Return an array of FileItem objects for the given name.  If the
358      * name does not exist, return null.
359      *
360      * @param name A String with the name.
361      * @return An Array of  FileItems or null.
362      */
363     public FileItem[] getFileItems(String name)
364     {
365         return getFileItemParam(name);
366     }
367 
368     /***
369      * Puts a key into the parameters map. Makes sure that the name is always
370      * mapped correctly. This method also enforces the usage of arrays for the
371      * parameters.
372      *
373      * @param name A String with the name.
374      * @param value An array of Objects with the values.
375      *
376      */
377     protected void putFileItemParam(final String name, final FileItem [] value)
378     {
379         String key = convert(name);
380         if (key != null)
381         {
382             fileParameters.put(key, value);
383         }
384     }
385 
386     /***
387      * fetches a key from the parameters map. Makes sure that the name is
388      * always mapped correctly.
389      *
390      * @param name A string with the name
391      *
392      * @return the value object array or null if not set
393      */
394     protected FileItem [] getFileItemParam(final String name)
395     {
396         String key = convert(name);
397 
398         return (key != null) ? (FileItem []) fileParameters.get(key) : null;
399     }
400 
401     /***
402      * This method is only used in toString() and can be used by
403      * derived classes to add their local parameters to the toString()
404 
405      * @param name A string with the name
406      *
407      * @return the value object array or null if not set
408      */
409     protected Object [] getToStringParam(final String name)
410     {
411         if (super.containsKey(name))
412         {
413             return getParam(name);
414         }
415         else
416         {
417             return getFileItemParam(name);
418         }
419     }
420 }