View Javadoc
1   package org.apache.fulcrum.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.io.IOException;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.servlet.ServletException;
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.Part;
29  
30  import org.apache.avalon.framework.configuration.Configurable;
31  import org.apache.avalon.framework.configuration.Configuration;
32  import org.apache.avalon.framework.configuration.ConfigurationException;
33  import org.apache.avalon.framework.logger.AbstractLogEnabled;
34  import org.apache.avalon.framework.logger.LogEnabled;
35  import org.apache.avalon.framework.service.ServiceException;
36  import org.apache.avalon.framework.service.ServiceManager;
37  import org.apache.avalon.framework.service.Serviceable;
38  import org.apache.commons.lang3.StringUtils;
39  import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
40  import org.apache.fulcrum.parser.ValueParser.URLCaseFolding;
41  import org.apache.fulcrum.parser.pool.BaseValueParserFactory;
42  import org.apache.fulcrum.parser.pool.BaseValueParserPool;
43  import org.apache.fulcrum.parser.pool.CookieParserFactory;
44  import org.apache.fulcrum.parser.pool.CookieParserPool;
45  import org.apache.fulcrum.parser.pool.DefaultParameterParserFactory;
46  import org.apache.fulcrum.parser.pool.DefaultParameterParserPool;
47  import org.apache.fulcrum.pool.PoolException;
48  import org.apache.fulcrum.pool.PoolService;
49  
50  
51  /**
52   * The DefaultParserService provides the default implementation
53   * of a {@link ParserService}.
54   *
55   * @author <a href="mailto:tv@apache.org">Thomas Vandahl</a>
56   * @version $Id: BaseValueParser.java 542062 2007-05-28 00:29:43Z seade $
57   */
58  public class DefaultParserService
59      extends AbstractLogEnabled
60      implements ParserService,
61                 Configurable, Serviceable
62  {
63  
64      /** The folding from the configuration */
65      private URLCaseFolding folding = URLCaseFolding.NONE;
66  
67      /** The automaticUpload setting from the configuration */
68      private boolean automaticUpload = AUTOMATIC_DEFAULT;
69  
70      /**
71       * The parameter encoding to use when parsing parameter strings
72       */
73      private String parameterEncoding = PARAMETER_ENCODING_DEFAULT;
74      
75      /**
76       * reintroduced fulcrum, may be used in the case, that 
77       * commons pool2 is not configured exactly as needed properly as fast fall back.
78       */
79      private boolean useFulcrumPool = FULCRUM_POOL_DEFAULT;
80      
81      
82      /**
83       * The Fulcrum pool service component to use (optional), by default it is deactivated and commons pool is used.
84       */
85      private PoolService fulcrumPoolService = null;
86  
87      /** 
88       * Use commons pool to manage value parsers 
89       */
90      private BaseValueParserPool valueParserPool;
91  
92      /** 
93       * Use commons pool to manage parameter parsers 
94       */
95      private DefaultParameterParserPool parameterParserPool;
96  
97      /** 
98       * Use commons pool to manage cookie parsers 
99       */
100     private CookieParserPool cookieParserPool;
101 
102 
103     public DefaultParserService() 
104     {
105     }
106     
107     public DefaultParserService(GenericObjectPoolConfig<?> config) 
108     {
109 	    // init the pool
110 	    valueParserPool 
111     		= new BaseValueParserPool(new BaseValueParserFactory(), config);
112 
113 	    parameterParserPool 
114 	    	= new DefaultParameterParserPool(new DefaultParameterParserFactory(), config);
115     }
116 
117     
118     /**
119      * Get the character encoding that will be used by this ValueParser.
120      */
121     @Override
122     public String getParameterEncoding()
123     {
124         return parameterEncoding;
125     }
126     
127     /**
128      * Set the character encoding that will be used by this ValueParser.
129      */
130     public void setParameterEncoding(String encoding)
131     {
132         parameterEncoding = encoding;
133     }
134 
135     /**
136      * Trims the string data and applies the conversion specified in
137      * the property given by URL_CASE_FOLDING.  It returns a new
138      * string so that it does not destroy the value data.
139      *
140      * @param value A String to be processed.
141      * @return A new String converted to the case as specified by URL_CASE_FOLDING and trimmed.
142      */
143     @Override
144     public String convert(String value)
145     {
146         return convertAndTrim(value);
147     }
148 
149     /**
150      * Convert a String value according to the url-case-folding property.
151      *
152      * @param value the String to convert
153      *
154      * @return a new String.
155      *
156      */
157     @Override
158     public String convertAndTrim(String value)
159     {
160         return convertAndTrim(value, getUrlFolding());
161     }
162 
163     /**
164      * A static version of the convert method, which
165      * trims the string data and applies the conversion specified in
166      * the property given by URL_CASE_FOLDING.  It returns a new
167      * string so that it does not destroy the value data.
168      *
169      * @param value A String to be processed.
170      * @return A new String converted to lowercase and trimmed.
171      */
172     @Override
173     public String convertAndTrim(String value, URLCaseFolding fold)
174     {
175         if (value == null)
176         {
177             return "";
178         }
179 
180         String tmp = value.trim();
181 
182         switch (fold)
183         {
184             case NONE:
185             {
186                 break;
187             }
188 
189             case LOWER:
190             {
191                 tmp = tmp.toLowerCase();
192                 break;
193             }
194 
195             case UPPER:
196             {
197                 tmp = tmp.toUpperCase();
198                 break;
199             }
200 
201             default:
202             {
203                 getLogger().error("Passed " + fold + " as fold rule, which is illegal!");
204                 break;
205             }
206         }
207         return tmp;
208     }
209 
210     /**
211      * Gets the folding value from the configuration
212      *
213      * @return The current Folding Value
214      */
215     @Override
216     public URLCaseFolding getUrlFolding()
217     {
218         return folding;
219     }
220 
221     /**
222      * Gets the automaticUpload value from the configuration
223      *
224      * @return The current automaticUpload Value
225      */
226     @Override
227     public boolean getAutomaticUpload()
228     {
229         return automaticUpload;
230     }
231 
232     /**
233      * Parse the given request for uploaded files
234      *
235      * @return A list of {@link javax.servlet.http.Part}s
236      *
237      * @throws ServiceException if parsing fails
238      */
239     @Override
240     public List<Part> parseUpload(HttpServletRequest request) throws ServiceException
241     {
242         try
243         {
244             return new ArrayList<Part>(request.getParts());
245         }
246         catch (IOException | ServletException e)
247         {
248             throw new ServiceException(ParserService.ROLE, "Could not parse upload request", e);
249         }
250     }
251 
252     /**
253      * Get a {@link ValueParser} instance from the service. Use the
254      * given Class to create the object.
255      *
256      * @return An object that implements ValueParser
257      *
258      * @throws InstantiationException if the instance could not be created
259      */
260     @SuppressWarnings("unchecked")
261 	@Override
262     public <P extends ValueParser> P getParser(Class<P> ppClass) throws InstantiationException
263     {
264         P vp = null;
265 
266         try
267         {
268             if (useFulcrumPool) {
269                 try
270                 {
271                     P parserInstance = (P) fulcrumPoolService.getInstance(ppClass);
272                     vp = parserInstance;
273                 }
274                 catch (PoolException pe)
275                 {
276                     throw new InstantiationException("Parser class '" + ppClass + "' is illegal. " + pe.getMessage());
277                 }
278             } else if ( ppClass.equals(BaseValueParser.class) )
279             {
280             	BaseValueParser parserInstance = null;
281 				try {
282 				    parserInstance = valueParserPool.borrowObject();
283 					vp = (P) parserInstance;
284 					if (vp == null) {
285                         throw new InstantiationException("Could not borrow object from pool: " + valueParserPool);
286                     }
287 				} catch (Exception e) {
288                     try {
289                         valueParserPool.invalidateObject(parserInstance);
290                         parserInstance = null;
291                     } catch (Exception e1) {
292                         throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
293                     }
294 				}
295             } else if ( ppClass.equals(DefaultParameterParser.class) )
296             {
297                 DefaultParameterParser parserInstance = null;
298                 try {
299                     parserInstance = parameterParserPool.borrowObject();
300                 	vp = (P) parserInstance;
301                 	if (vp == null) {
302                         throw new InstantiationException("Could not borrow object from pool: " + parameterParserPool);
303                     }
304                 } catch (Exception e) {
305                     try {
306                         parameterParserPool.invalidateObject(parserInstance);
307                         parserInstance = null;
308                     } catch (Exception e1) {
309                         throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
310                     }
311                 }
312             } else if ( ppClass.equals(DefaultCookieParser.class) )
313             {
314                 DefaultCookieParser parserInstance = null;
315                 try {
316                     parserInstance = cookieParserPool.borrowObject();
317                 	vp = (P) parserInstance;
318                     if (vp == null) {
319                           throw new InstantiationException("Could not borrow object from pool: " + cookieParserPool);
320                     }
321                 } catch (Exception e) {
322                     try {
323                         cookieParserPool.invalidateObject(parserInstance);
324                         parserInstance = null;
325                     } catch (Exception e1) {
326                         throw new InstantiationException("Could not invalidate object " + e1.getMessage() + " after exception: " + e.getMessage());
327                     }
328                 }
329             }
330             
331             if (vp != null && vp instanceof ParserServiceSupport ) {
332                 ((ParserServiceSupport)vp).setParserService(this);
333             } else {
334                 throw new InstantiationException("Could not set parser");
335             }
336             if (vp instanceof LogEnabled) 
337             {
338                 ((LogEnabled)vp).enableLogging(getLogger().getChildLogger(ppClass.getSimpleName()));
339             }
340         }
341         catch (ClassCastException x)
342         {
343             throw new InstantiationException("Parser class '" + ppClass + "' is illegal. " + x.getMessage());
344         }
345 
346         return vp;
347     }
348 
349     /**
350      * Clears the parse and puts it back into
351      * the pool service. This allows for pooling 
352      * and recycling
353      * 
354      * As we are not yet using org.apache.fulcrum.pool.Recyclable, we call insteda {@link ValueParser#dispose()}.
355      *
356      * @param parser The value parser to use
357      */
358     @Override
359     public void putParser(ValueParser parser)
360     {
361         parser.clear();
362         parser.dispose(); 
363     
364         if (useFulcrumPool) {
365             
366             fulcrumPoolService.putInstance(parser);
367             
368         } else if( parser.getClass().equals(BaseValueParser.class) )
369         {
370             valueParserPool.returnObject( (BaseValueParser) parser );
371         
372         } else if ( parser.getClass().equals(DefaultParameterParser.class) ||
373                 parser instanceof DefaultParameterParser)
374         {
375             parameterParserPool.returnObject( (DefaultParameterParser) parser );
376         	
377         } else if ( parser.getClass().equals(DefaultCookieParser.class) ||
378                 parser instanceof DefaultCookieParser)
379         {
380             cookieParserPool.returnObject( (DefaultCookieParser) parser );
381         	
382         } else {
383             // log
384             getLogger().warn(parser.getClass() + " could not be put back into any pool exhausting some pool");
385             // log even borrowed count of each pool?: cookieParserPool.getBorrowedCount())
386             }
387     }
388 
389     /**
390      * Avalon component lifecycle method
391      * 
392      * @param conf the configuration
393      * @throws ConfigurationException Generic exception
394      */
395     @Override
396     public void configure(Configuration conf) throws ConfigurationException
397     {
398         String foldString = conf.getChild(URL_CASE_FOLDING_KEY).getValue(URLCaseFolding.NONE.name()).toLowerCase();
399 
400         folding = URLCaseFolding.NONE;
401 
402         getLogger().debug("Setting folding from " + foldString);
403 
404         if (StringUtils.isNotEmpty(foldString))
405         {
406             try
407             {
408                 folding = URLCaseFolding.valueOf(foldString.toUpperCase());
409             }
410             catch (IllegalArgumentException e)
411             {
412                 getLogger().error("Got " + foldString + " from " + URL_CASE_FOLDING_KEY + " property, which is illegal!");
413                 throw new ConfigurationException("Value " + foldString + " is illegal!", e);
414             }
415         }
416 
417         parameterEncoding = conf.getChild(PARAMETER_ENCODING_KEY)
418                             .getValue(PARAMETER_ENCODING_DEFAULT).toLowerCase();
419 
420         automaticUpload = conf.getChild(AUTOMATIC_KEY).getValueAsBoolean(AUTOMATIC_DEFAULT);
421         
422         useFulcrumPool = conf.getChild(FULCRUM_POOL_KEY).getValueAsBoolean(FULCRUM_POOL_DEFAULT);
423         
424         if (useFulcrumPool) {
425             if (fulcrumPoolService == null) 
426             {
427                     // only for fulcrum  pool, need to call internal service, if role pool service is set
428                     throw new ConfigurationException("Fulcrum Pool is activated", new ServiceException(ParserService.ROLE,
429                             "Fulcrum enabled Pool Service requires " +
430                             PoolService.ROLE + " to be available"));
431             }
432             getLogger().info("Using Fulcrum Pool Service: "+ fulcrumPoolService);
433         } else {
434             // reset not used fulcrum pool
435             fulcrumPoolService = null;
436             
437             // Define the default configuration
438             GenericObjectPoolConfig config = new GenericObjectPoolConfig();
439             config.setMaxIdle(DEFAULT_MAX_IDLE);
440             config.setMaxTotal(DEFAULT_POOL_CAPACITY);
441 
442             // init the pool
443             valueParserPool 
444                 = new BaseValueParserPool(new BaseValueParserFactory(), config);
445 
446             // init the pool
447             parameterParserPool 
448                 = new DefaultParameterParserPool(new DefaultParameterParserFactory(), config);
449             
450             // init the pool
451             cookieParserPool 
452                 = new CookieParserPool(new CookieParserFactory(), config);
453             
454             getLogger().info("Init Commons2 Pool Services.." );
455             getLogger().info(valueParserPool.getClass().getName());
456             getLogger().info(parameterParserPool.getClass().getName());
457             getLogger().info(cookieParserPool.getClass().getName());
458         }
459         
460         Configuration[] poolChildren = conf.getChild(POOL_KEY).getChildren();
461         if (poolChildren.length > 0) {
462             GenericObjectPoolConfig genObjPoolConfig = new GenericObjectPoolConfig();
463             genObjPoolConfig.setMaxIdle(DEFAULT_MAX_IDLE);
464             genObjPoolConfig.setMaxTotal(DEFAULT_POOL_CAPACITY);
465             for (Configuration poolConf : poolChildren) {
466                 // use common pool2 configuration names
467                 switch (poolConf.getName()) {
468                 case "maxTotal":
469                     int defaultCapacity = poolConf.getValueAsInteger();
470                     genObjPoolConfig.setMaxTotal(defaultCapacity);
471                     break;
472                 case "maxWaitMillis":
473                     int maxWaitMillis = poolConf.getValueAsInteger();
474                     genObjPoolConfig.setMaxWaitMillis(maxWaitMillis);
475                     break;
476                 case "blockWhenExhausted":
477                     boolean blockWhenExhausted = poolConf.getValueAsBoolean();
478                     genObjPoolConfig.setBlockWhenExhausted(blockWhenExhausted);
479                     break;
480                 case "maxIdle":
481                     int maxIdle = poolConf.getValueAsInteger();
482                     genObjPoolConfig.setMaxIdle(maxIdle);
483                     break;
484                 case "minIdle":
485                     int minIdle = poolConf.getValueAsInteger();
486                     genObjPoolConfig.setMinIdle(minIdle);
487                     break;
488                 case "testOnReturn":
489                     boolean testOnReturn = poolConf.getValueAsBoolean();
490                     genObjPoolConfig.setTestOnReturn(testOnReturn);
491                     break;
492                 case "testOnBorrow":
493                     boolean testOnBorrow = poolConf.getValueAsBoolean();
494                     genObjPoolConfig.setTestOnBorrow(testOnBorrow);
495                     break;
496                 case "testOnCreate":
497                     boolean testOnCreate = poolConf.getValueAsBoolean();
498                     genObjPoolConfig.setTestOnCreate(testOnCreate);
499                     break;
500                 default:
501                     
502                     break;
503                 }  
504             }    
505             // reinit the pools
506             valueParserPool.setConfig(genObjPoolConfig);
507             parameterParserPool.setConfig(genObjPoolConfig);
508             cookieParserPool.setConfig(genObjPoolConfig);
509             
510             getLogger().debug(valueParserPool.toString());
511             getLogger().debug(parameterParserPool.toString());
512             getLogger().debug(cookieParserPool.toString());
513         }
514                 
515     }
516 
517     // ---------------- Avalon Lifecycle Methods ---------------------
518     /**
519      * Avalon component lifecycle method
520      * 
521      * @param manager The service manager instance
522      * @throws ServiceException generic exception
523      * 
524      */
525     @Override
526     public void service(ServiceManager manager) throws ServiceException
527     {
528         // only for fulcrum  pool, need to call internal service, if role pool service is set
529         if (manager.hasService(PoolService.ROLE))
530         {
531             fulcrumPoolService = (PoolService)manager.lookup(PoolService.ROLE);
532         } 
533         // no check here, until configuration is read
534     }
535 }