    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}