View Javadoc

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