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.Iterator;
023import java.util.Locale;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ConcurrentMap;
026
027import javax.servlet.ServletConfig;
028import javax.servlet.ServletContext;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031
032import org.apache.commons.configuration2.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;
047import org.apache.turbine.util.TurbineRuntimeException;
048
049/**
050 * The RunData Service provides the implementations for RunData and
051 * related interfaces required by request processing. It supports
052 * different configurations of implementations, which can be selected
053 * by specifying a configuration key. It may use pooling, in which case
054 * the implementations should implement the Recyclable interface.
055 *
056 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
057 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
058 * @version $Id$
059 */
060public class TurbineRunDataService
061    extends TurbineBaseService
062    implements RunDataService
063{
064
065    /** The default implementation of the RunData object*/
066    private static final String DEFAULT_RUN_DATA =
067        DefaultTurbineRunData.class.getName();
068
069    /** The default implementation of the Parameter Parser object */
070    private static final String DEFAULT_PARAMETER_PARSER =
071        DefaultParameterParser.class.getName();
072
073    /** The default implementation of the Cookie parser object */
074    private static final String DEFAULT_COOKIE_PARSER =
075        DefaultCookieParser.class.getName();
076
077    /** The map of configurations. */
078    private final ConcurrentMap<String, Object> configurations = new ConcurrentHashMap<>();
079
080    /** A class cache. */
081    private final ConcurrentMap<String, Class<?>> classCache = new ConcurrentHashMap<>();
082
083    /** Private reference to the pool service for object recycling */
084    private PoolService pool = null;
085
086    /** Private reference to the parser service for parser recycling */
087    private ParserService parserService = null;
088
089    /**
090     * Constructs a RunData Service.
091     */
092    public TurbineRunDataService()
093    {
094        super();
095    }
096
097    /**
098     * Initializes the service by setting the pool capacity.
099     *
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}