View Javadoc
1   package org.apache.turbine.services.rundata;
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.util.Iterator;
23  import java.util.Locale;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  
27  import javax.servlet.ServletConfig;
28  import javax.servlet.ServletContext;
29  import javax.servlet.http.HttpServletRequest;
30  import javax.servlet.http.HttpServletResponse;
31  
32  import org.apache.commons.configuration2.Configuration;
33  import org.apache.fulcrum.parser.CookieParser;
34  import org.apache.fulcrum.parser.DefaultCookieParser;
35  import org.apache.fulcrum.parser.DefaultParameterParser;
36  import org.apache.fulcrum.parser.ParameterParser;
37  import org.apache.fulcrum.parser.ParserService;
38  import org.apache.fulcrum.pool.PoolException;
39  import org.apache.fulcrum.pool.PoolService;
40  import org.apache.turbine.Turbine;
41  import org.apache.turbine.services.InitializationException;
42  import org.apache.turbine.services.TurbineBaseService;
43  import org.apache.turbine.services.TurbineServices;
44  import org.apache.turbine.util.RunData;
45  import org.apache.turbine.util.ServerData;
46  import org.apache.turbine.util.TurbineException;
47  import org.apache.turbine.util.TurbineRuntimeException;
48  
49  /**
50   * The RunData Service provides the implementations for RunData and
51   * related interfaces required by request processing. It supports
52   * different configurations of implementations, which can be selected
53   * by specifying a configuration key. It may use pooling, in which case
54   * the implementations should implement the Recyclable interface.
55   *
56   * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
57   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
58   * @version $Id$
59   */
60  public class TurbineRunDataService
61      extends TurbineBaseService
62      implements RunDataService
63  {
64  
65      /** The default implementation of the RunData object*/
66      private static final String DEFAULT_RUN_DATA =
67          DefaultTurbineRunData.class.getName();
68  
69      /** The default implementation of the Parameter Parser object */
70      private static final String DEFAULT_PARAMETER_PARSER =
71          DefaultParameterParser.class.getName();
72  
73      /** The default implementation of the Cookie parser object */
74      private static final String DEFAULT_COOKIE_PARSER =
75          DefaultCookieParser.class.getName();
76  
77      /** The map of configurations. */
78      private final ConcurrentMap<String, Object> configurations = new ConcurrentHashMap<>();
79  
80      /** A class cache. */
81      private final ConcurrentMap<String, Class<?>> classCache = new ConcurrentHashMap<>();
82  
83      /** Private reference to the pool service for object recycling */
84      private PoolService pool = null;
85  
86      /** Private reference to the parser service for parser recycling */
87      private ParserService parserService = null;
88  
89      /**
90       * Constructs a RunData Service.
91       */
92      public TurbineRunDataService()
93      {
94          super();
95      }
96  
97      /**
98       * Initializes the service by setting the pool capacity.
99       *
100      * @throws InitializationException if initialization fails.
101      */
102     @Override
103     public void init()
104             throws InitializationException
105     {
106         // Create a default configuration.
107         String[] def = new String[]
108         {
109             DEFAULT_RUN_DATA,
110             DEFAULT_PARAMETER_PARSER,
111             DEFAULT_COOKIE_PARSER
112         };
113         configurations.put(DEFAULT_CONFIG, def.clone());
114 
115         // Check other configurations.
116         Configuration conf = getConfiguration();
117         if (conf != null)
118         {
119             String key,value;
120             String[] config;
121             String[] plist = new String[]
122             {
123                 RUN_DATA_KEY,
124                 PARAMETER_PARSER_KEY,
125                 COOKIE_PARSER_KEY
126             };
127             for (Iterator<String> i = conf.getKeys(); i.hasNext();)
128             {
129                 key = i.next();
130                 value = conf.getString(key);
131                 int j = 0;
132                 for (String plistKey : plist)
133                 {
134                     if (key.endsWith(plistKey) && key.length() > plistKey.length() + 1)
135                     {
136                         key = key.substring(0, key.length() - plistKey.length() - 1);
137                         config = (String[]) configurations.get(key);
138                         if (config == null)
139                         {
140                             config = def.clone();
141                             configurations.put(key, config);
142                         }
143                         config[j] = value;
144                         break;
145                     }
146                     j++;
147                 }
148             }
149         }
150 
151 		pool = (PoolService)TurbineServices.getInstance().getService(PoolService.ROLE);
152 
153         if (pool == null)
154         {
155             throw new InitializationException("RunData Service requires"
156                 + " configured Pool Service!");
157         }
158 
159         parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
160 
161         if (parserService == null)
162         {
163             throw new InitializationException("RunData Service requires"
164                 + " configured Parser Service!");
165         }
166 
167         setInit(true);
168     }
169 
170     /**
171      * Shutdown the service
172      *
173      * @see org.apache.turbine.services.TurbineBaseService#shutdown()
174      */
175     @Override
176     public void shutdown()
177     {
178         classCache.clear();
179         super.shutdown();
180     }
181 
182     /**
183      * Gets a default RunData object.
184      *
185      * @param req a servlet request.
186      * @param res a servlet response.
187      * @param config a servlet config.
188      * @return a new or recycled RunData object.
189      * @throws TurbineException if the operation fails.
190      */
191     @Override
192     public RunData getRunData(HttpServletRequest req,
193                               HttpServletResponse res,
194                               ServletConfig config)
195             throws TurbineException
196     {
197         return getRunData(DEFAULT_CONFIG, req, res, config);
198     }
199 
200     /**
201      * Gets a RunData instance from a specific configuration.
202      *
203      * @param key a configuration key.
204      * @param req a servlet request.
205      * @param res a servlet response.
206      * @param config a servlet config.
207      * @return a new or recycled RunData object.
208      * @throws TurbineException if the operation fails.
209      * @throws IllegalArgumentException if any of the parameters are null.
210      * TODO The "key" parameter should be removed in favor of just looking up what class via the roleConfig avalon file.
211      */
212     @Override
213     public RunData getRunData(String key,
214                               HttpServletRequest req,
215                               HttpServletResponse res,
216                               ServletConfig config)
217             throws TurbineException,
218             IllegalArgumentException
219     {
220         // The RunData object caches all the information that is needed for
221         // the execution lifetime of a single request. A RunData object
222         // is created/recycled for each and every request and is passed
223         // to each and every module. Since each thread has its own RunData
224         // object, it is not necessary to perform synchronization for
225         // the data within this object.
226         if (req == null || res == null || config == null)
227         {
228             throw new IllegalArgumentException("HttpServletRequest, "
229                 + "HttpServletResponse or ServletConfig was null.");
230         }
231 
232         // Get the specified configuration.
233         String[] cfg = (String[]) configurations.get(key);
234         if (cfg == null)
235         {
236             throw new TurbineException("RunTime configuration '" + key + "' is undefined");
237         }
238 
239         TurbineRunData data;
240         try
241         {
242     		Class<?> runDataClazz = classCache.computeIfAbsent(cfg[0], this::classForName);
243             Class<?> parameterParserClazz = classCache.computeIfAbsent(cfg[1], this::classForName);
244             Class<?> cookieParserClazz = classCache.computeIfAbsent(cfg[2], this::classForName);
245 
246             data = (TurbineRunData) pool.getInstance(runDataClazz);
247             @SuppressWarnings("unchecked") // ok
248             ParameterParser pp = parserService.getParser((Class<ParameterParser>)parameterParserClazz);
249             data.get(Turbine.class).put(ParameterParser.class, pp);
250 
251             @SuppressWarnings("unchecked") // ok
252             CookieParser cp = parserService.getParser((Class<CookieParser>)cookieParserClazz);
253             data.get(Turbine.class).put(CookieParser.class, cp);
254 
255             Locale locale = req.getLocale();
256 
257             if (locale == null)
258             {
259                 // get the default from the Turbine configuration
260                 locale = data.getLocale();
261             }
262 
263             // set the locale detected and propagate it to the parsers
264             data.setLocale(locale);
265         }
266         catch (PoolException pe)
267         {
268             throw new TurbineException("RunData configuration '" + key + "' is illegal caused a pool exception", pe);
269         }
270         catch (TurbineRuntimeException | ClassCastException | InstantiationException x)
271         {
272             throw new TurbineException("RunData configuration '" + key + "' is illegal", x);
273         }
274 
275         // Set the request and response.
276         data.get(Turbine.class).put(HttpServletRequest.class, req);
277         data.get(Turbine.class).put(HttpServletResponse.class, res);
278 
279         // Set the servlet configuration.
280         data.get(Turbine.class).put(ServletConfig.class, config);
281         data.get(Turbine.class).put(ServletContext.class, config.getServletContext());
282 
283         // Set the ServerData.
284         data.get(Turbine.class).put(ServerData.class, new ServerData(req));
285 
286         return data;
287     }
288 
289     /**
290      * Puts the used RunData object back to the factory for recycling.
291      *
292      * @param data the used RunData object.
293      * @return true, if pooling is supported and the object was accepted.
294      */
295     @Override
296     public boolean putRunData(RunData data)
297     {
298         if (data instanceof TurbineRunData)
299         {
300             parserService.putParser(((TurbineRunData) data).getParameterParser());
301             parserService.putParser(((TurbineRunData) data).getCookieParser());
302 
303             return pool.putInstance(data);
304         }
305         else
306         {
307             return false;
308         }
309     }
310 
311     @SuppressWarnings("unchecked") // ok
312     private <T> Class<T> classForName(String className) throws TurbineRuntimeException
313     {
314         try
315         {
316             return (Class<T>) Class.forName(className);
317         }
318         catch (ClassNotFoundException e)
319         {
320             throw new TurbineRuntimeException("Could not load class " + className, e);
321         }
322     }
323 }