001package org.apache.turbine.services.rundata;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.Locale;
025import java.util.Map;
026
027import javax.servlet.ServletConfig;
028import javax.servlet.ServletContext;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031
032import org.apache.commons.configuration.Configuration;
033import org.apache.fulcrum.parser.CookieParser;
034import org.apache.fulcrum.parser.DefaultCookieParser;
035import org.apache.fulcrum.parser.DefaultParameterParser;
036import org.apache.fulcrum.parser.ParameterParser;
037import org.apache.fulcrum.parser.ParserService;
038import org.apache.fulcrum.pool.PoolException;
039import org.apache.fulcrum.pool.PoolService;
040import org.apache.turbine.Turbine;
041import org.apache.turbine.services.InitializationException;
042import org.apache.turbine.services.TurbineBaseService;
043import org.apache.turbine.services.TurbineServices;
044import org.apache.turbine.util.RunData;
045import org.apache.turbine.util.ServerData;
046import org.apache.turbine.util.TurbineException;
047
048/**
049 * The RunData Service provides the implementations for RunData and
050 * related interfaces required by request processing. It supports
051 * different configurations of implementations, which can be selected
052 * by specifying a configuration key. It may use pooling, in which case
053 * the implementations should implement the Recyclable interface.
054 *
055 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
056 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
057 * @version $Id: TurbineRunDataService.java 1706239 2015-10-01 13:18:35Z tv $
058 */
059public class TurbineRunDataService
060    extends TurbineBaseService
061    implements RunDataService
062{
063
064    /** The default implementation of the RunData object*/
065    private static final String DEFAULT_RUN_DATA =
066        DefaultTurbineRunData.class.getName();
067
068    /** The default implementation of the Parameter Parser object */
069    private static final String DEFAULT_PARAMETER_PARSER =
070        DefaultParameterParser.class.getName();
071
072    /** The default implementation of the Cookie parser object */
073    private static final String DEFAULT_COOKIE_PARSER =
074        DefaultCookieParser.class.getName();
075
076    /** The map of configurations. */
077    private final Map<String, Object> configurations = new HashMap<String, Object>();
078
079    /** A class cache. */
080    private final Map<String, Class<?>> classCache = new HashMap<String, Class<?>>();
081
082    /** Private reference to the pool service for object recycling */
083    private PoolService pool = null;
084
085    /** Private reference to the parser service for parser recycling */
086    private ParserService parserService = null;
087
088    /**
089     * Constructs a RunData Service.
090     */
091    public TurbineRunDataService()
092    {
093        super();
094    }
095
096    /**
097     * Initializes the service by setting the pool capacity.
098     *
099     * @throws InitializationException if initialization fails.
100     */
101    @Override
102    public void init()
103            throws InitializationException
104    {
105        // Create a default configuration.
106        String[] def = new String[]
107        {
108            DEFAULT_RUN_DATA,
109            DEFAULT_PARAMETER_PARSER,
110            DEFAULT_COOKIE_PARSER
111        };
112        configurations.put(DEFAULT_CONFIG, def.clone());
113
114        // Check other configurations.
115        Configuration conf = getConfiguration();
116        if (conf != null)
117        {
118            String key,value;
119            String[] config;
120            String[] plist = new String[]
121            {
122                RUN_DATA_KEY,
123                PARAMETER_PARSER_KEY,
124                COOKIE_PARSER_KEY
125            };
126            for (Iterator<String> i = conf.getKeys(); i.hasNext();)
127            {
128                key = i.next();
129                value = conf.getString(key);
130                for (int j = 0; j < plist.length; j++)
131                {
132                    if (key.endsWith(plist[j]) &&
133                            (key.length() > (plist[j].length() + 1)))
134                    {
135                        key = key.substring(0, key.length() - plist[j].length() - 1);
136                        config = (String[]) configurations.get(key);
137                        if (config == null)
138                        {
139                            config = def.clone();
140                            configurations.put(key, config);
141                        }
142                        config[j] = value;
143                        break;
144                    }
145                }
146            }
147        }
148
149                pool = (PoolService)TurbineServices.getInstance().getService(PoolService.ROLE);
150
151        if (pool == null)
152        {
153            throw new InitializationException("RunData Service requires"
154                + " configured Pool Service!");
155        }
156
157        parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
158
159        if (parserService == null)
160        {
161            throw new InitializationException("RunData Service requires"
162                + " configured Parser Service!");
163        }
164
165        setInit(true);
166    }
167
168    /**
169     * Shutdown the service
170     *
171     * @see org.apache.turbine.services.TurbineBaseService#shutdown()
172     */
173    @Override
174    public void shutdown()
175    {
176        classCache.clear();
177        super.shutdown();
178    }
179
180    /**
181     * Gets a default RunData object.
182     *
183     * @param req a servlet request.
184     * @param res a servlet response.
185     * @param config a servlet config.
186     * @return a new or recycled RunData object.
187     * @throws TurbineException if the operation fails.
188     */
189    @Override
190    public RunData getRunData(HttpServletRequest req,
191                              HttpServletResponse res,
192                              ServletConfig config)
193            throws TurbineException
194    {
195        return getRunData(DEFAULT_CONFIG, req, res, config);
196    }
197
198    /**
199     * Gets a RunData instance from a specific configuration.
200     *
201     * @param key a configuration key.
202     * @param req a servlet request.
203     * @param res a servlet response.
204     * @param config a servlet config.
205     * @return a new or recycled RunData object.
206     * @throws TurbineException if the operation fails.
207     * @throws IllegalArgumentException if any of the parameters are null.
208     * TODO The "key" parameter should be removed in favor of just looking up what class via the roleConfig avalon file.
209     */
210    @Override
211    public RunData getRunData(String key,
212                              HttpServletRequest req,
213                              HttpServletResponse res,
214                              ServletConfig config)
215            throws TurbineException,
216            IllegalArgumentException
217    {
218        // The RunData object caches all the information that is needed for
219        // the execution lifetime of a single request. A RunData object
220        // is created/recycled for each and every request and is passed
221        // to each and every module. Since each thread has its own RunData
222        // object, it is not necessary to perform synchronization for
223        // the data within this object.
224        if ((req == null)
225            || (res == null)
226            || (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.get(cfg[0]);
243                if (runDataClazz == null)
244                {
245                    runDataClazz = Class.forName(cfg[0]);
246                    classCache.put(cfg[0], runDataClazz);
247                }
248
249            Class<?> parameterParserClazz = classCache.get(cfg[1]);
250            if (parameterParserClazz == null)
251            {
252                parameterParserClazz = Class.forName(cfg[1]);
253                classCache.put(cfg[1], parameterParserClazz);
254            }
255
256            Class<?> cookieParserClazz = classCache.get(cfg[2]);
257            if (cookieParserClazz == null)
258            {
259                cookieParserClazz = Class.forName(cfg[2]);
260                classCache.put(cfg[2], cookieParserClazz);
261            }
262
263            data = (TurbineRunData) pool.getInstance(runDataClazz);
264            @SuppressWarnings("unchecked") // ok
265            ParameterParser pp = parserService.getParser((Class<ParameterParser>)parameterParserClazz);
266            data.get(Turbine.class).put(ParameterParser.class, pp);
267
268            @SuppressWarnings("unchecked") // ok
269            CookieParser cp = parserService.getParser((Class<CookieParser>)cookieParserClazz);
270            data.get(Turbine.class).put(CookieParser.class, cp);
271
272            Locale locale = req.getLocale();
273
274            if (locale == null)
275            {
276                // get the default from the Turbine configuration
277                locale = data.getLocale();
278            }
279
280            // set the locale detected and propagate it to the parsers
281            data.setLocale(locale);
282        }
283        catch (PoolException pe)
284        {
285            throw new TurbineException("RunData configuration '" + key + "' is illegal caused a pool exception", pe);
286        }
287        catch (ClassNotFoundException x)
288        {
289            throw new TurbineException("RunData configuration '" + key + "' is illegal", x);
290        }
291        catch (ClassCastException x)
292        {
293            throw new TurbineException("RunData configuration '" + key + "' is illegal", x);
294        }
295        catch (InstantiationException e)
296        {
297            throw new TurbineException("RunData configuration '" + key + "' is illegal", e);
298        }
299
300        // Set the request and response.
301        data.get(Turbine.class).put(HttpServletRequest.class, req);
302        data.get(Turbine.class).put(HttpServletResponse.class, res);
303
304        // Set the servlet configuration.
305        data.get(Turbine.class).put(ServletConfig.class, config);
306        data.get(Turbine.class).put(ServletContext.class, config.getServletContext());
307
308        // Set the ServerData.
309        data.get(Turbine.class).put(ServerData.class, new ServerData(req));
310
311        return data;
312    }
313
314    /**
315     * Puts the used RunData object back to the factory for recycling.
316     *
317     * @param data the used RunData object.
318     * @return true, if pooling is supported and the object was accepted.
319     */
320    @Override
321    public boolean putRunData(RunData data)
322    {
323        if (data instanceof TurbineRunData)
324        {
325            parserService.putParser(((TurbineRunData) data).getParameterParser());
326            parserService.putParser(((TurbineRunData) data).getCookieParser());
327
328            return pool.putInstance(data);
329        }
330        else
331        {
332            return false;
333        }
334    }
335}