001    package 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    
022    import java.util.HashMap;
023    import java.util.Iterator;
024    import java.util.Locale;
025    import java.util.Map;
026    
027    import javax.servlet.ServletConfig;
028    import javax.servlet.http.HttpServletRequest;
029    import javax.servlet.http.HttpServletResponse;
030    
031    import org.apache.commons.configuration.Configuration;
032    import org.apache.fulcrum.parser.CookieParser;
033    import org.apache.fulcrum.parser.DefaultCookieParser;
034    import org.apache.fulcrum.parser.DefaultParameterParser;
035    import org.apache.fulcrum.parser.ParameterParser;
036    import org.apache.fulcrum.parser.ParserService;
037    import org.apache.fulcrum.pool.PoolException;
038    import org.apache.fulcrum.pool.PoolService;
039    import org.apache.turbine.Turbine;
040    import org.apache.turbine.services.InitializationException;
041    import org.apache.turbine.services.TurbineBaseService;
042    import org.apache.turbine.services.TurbineServices;
043    import org.apache.turbine.util.RunData;
044    import org.apache.turbine.util.ServerData;
045    import org.apache.turbine.util.TurbineException;
046    
047    /**
048     * The RunData Service provides the implementations for RunData and
049     * related interfaces required by request processing. It supports
050     * different configurations of implementations, which can be selected
051     * by specifying a configuration key. It may use pooling, in which case
052     * the implementations should implement the Recyclable interface.
053     *
054     * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a>
055     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
056     * @version $Id: TurbineRunDataService.java 1078552 2011-03-06 19:58:46Z tv $
057     */
058    public class TurbineRunDataService
059        extends TurbineBaseService
060        implements RunDataService
061    {
062    
063        /** The default implementation of the RunData object*/
064        private static final String DEFAULT_RUN_DATA =
065            DefaultTurbineRunData.class.getName();
066    
067        /** The default implementation of the Parameter Parser object */
068        private static final String DEFAULT_PARAMETER_PARSER =
069            DefaultParameterParser.class.getName();
070    
071        /** The default implementation of the Cookie parser object */
072        private static final String DEFAULT_COOKIE_PARSER =
073            DefaultCookieParser.class.getName();
074    
075        /** The map of configurations. */
076        private final Map<String, Object> configurations = new HashMap<String, Object>();
077    
078        /** Private reference to the pool service for object recycling */
079        private PoolService pool = null;
080    
081        /** Private reference to the parser service for parser recycling */
082        private ParserService parserService = null;
083    
084        /**
085         * Constructs a RunData Service.
086         */
087        public TurbineRunDataService()
088        {
089            super();
090        }
091    
092        /**
093         * Initializes the service by setting the pool capacity.
094         *
095         * @throws InitializationException if initialization fails.
096         */
097        @SuppressWarnings("unchecked")
098        @Override
099        public void init()
100                throws InitializationException
101        {
102            // Create a default configuration.
103            String[] def = new String[]
104            {
105                DEFAULT_RUN_DATA,
106                DEFAULT_PARAMETER_PARSER,
107                DEFAULT_COOKIE_PARSER
108            };
109            configurations.put(DEFAULT_CONFIG, def.clone());
110    
111            // Check other configurations.
112            Configuration conf = getConfiguration();
113            if (conf != null)
114            {
115                String key,value;
116                String[] config;
117                String[] plist = new String[]
118                {
119                    RUN_DATA_KEY,
120                    PARAMETER_PARSER_KEY,
121                    COOKIE_PARSER_KEY
122                };
123                for (Iterator<String> i = conf.getKeys(); i.hasNext();)
124                {
125                    key = i.next();
126                    value = conf.getString(key);
127                    for (int j = 0; j < plist.length; j++)
128                    {
129                        if (key.endsWith(plist[j]) &&
130                                (key.length() > (plist[j].length() + 1)))
131                        {
132                            key = key.substring(0, key.length() - plist[j].length() - 1);
133                            config = (String[]) configurations.get(key);
134                            if (config == null)
135                            {
136                                config = def.clone();
137                                configurations.put(key, config);
138                            }
139                            config[j] = value;
140                            break;
141                        }
142                    }
143                }
144            }
145    
146                    pool = (PoolService)TurbineServices.getInstance().getService(PoolService.ROLE);
147    
148            if (pool == null)
149            {
150                throw new InitializationException("RunData Service requires"
151                    + " configured Pool Service!");
152            }
153    
154            parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
155    
156            if (parserService == null)
157            {
158                throw new InitializationException("RunData Service requires"
159                    + " configured Parser Service!");
160            }
161    
162            setInit(true);
163        }
164    
165        /**
166         * Gets a default RunData object.
167         *
168         * @param req a servlet request.
169         * @param res a servlet response.
170         * @param config a servlet config.
171         * @return a new or recycled RunData object.
172         * @throws TurbineException if the operation fails.
173         */
174        public RunData getRunData(HttpServletRequest req,
175                                  HttpServletResponse res,
176                                  ServletConfig config)
177                throws TurbineException
178        {
179            return getRunData(DEFAULT_CONFIG, req, res, config);
180        }
181    
182        /**
183         * Gets a RunData instance from a specific configuration.
184         *
185         * @param key a configuration key.
186         * @param req a servlet request.
187         * @param res a servlet response.
188         * @param config a servlet config.
189         * @return a new or recycled RunData object.
190         * @throws TurbineException if the operation fails.
191         * @throws IllegalArgumentException if any of the parameters are null.
192         * @todo The "key" parameter should be removed in favor of just looking up what class via the roleConfig avalon file.
193         */
194        public RunData getRunData(String key,
195                                  HttpServletRequest req,
196                                  HttpServletResponse res,
197                                  ServletConfig config)
198                throws TurbineException,
199                IllegalArgumentException
200        {
201            // a map to hold information to be added to pipelineData
202            Map<Class<?>, Object> pipelineDataMap = new HashMap<Class<?>, Object>();
203            // The RunData object caches all the information that is needed for
204            // the execution lifetime of a single request. A RunData object
205            // is created/recycled for each and every request and is passed
206            // to each and every module. Since each thread has its own RunData
207            // object, it is not necessary to perform syncronization for
208            // the data within this object.
209            if ((req == null)
210                || (res == null)
211                || (config == null))
212            {
213                throw new IllegalArgumentException("HttpServletRequest, "
214                    + "HttpServletResponse or ServletConfig was null.");
215            }
216    
217            // Get the specified configuration.
218            String[] cfg = (String[]) configurations.get(key);
219            if (cfg == null)
220            {
221                throw new TurbineException("RunTime configuration '" + key + "' is undefined");
222            }
223    
224            TurbineRunData data;
225            try
226            {
227                    Class<?> runDataClazz = Class.forName(cfg[0]);
228                    Class<?> parameterParserClazz = Class.forName(cfg[1]);
229                    Class<?> cookieParserClazz = Class.forName(cfg[2]);
230    
231                data = (TurbineRunData) pool.getInstance(runDataClazz);
232                ParameterParser pp = (ParameterParser) parserService.getParser(parameterParserClazz);
233                data.setParameterParser(pp);
234                CookieParser cp = (CookieParser) parserService.getParser(cookieParserClazz);
235                data.setCookieParser(cp);
236                // also copy data directly into pipelineData
237                pipelineDataMap.put(ParameterParser.class, pp);
238                pipelineDataMap.put(CookieParser.class, cp);
239    
240                Locale locale = req.getLocale();
241    
242                if (locale == null)
243                {
244                    // get the default from the Turbine configuration
245                    locale = data.getLocale();
246                }
247    
248                // set the locale detected and propagate it to the parsers
249                data.setLocale(locale);
250            }
251            catch (PoolException pe)
252            {
253                throw new TurbineException("RunData configuration '" + key + "' is illegal caused a pool exception", pe);
254            }
255            catch (ClassNotFoundException x)
256            {
257                throw new TurbineException("RunData configuration '" + key + "' is illegal", x);
258            }
259            catch (ClassCastException x)
260            {
261                throw new TurbineException("RunData configuration '" + key + "' is illegal", x);
262            }
263            catch (InstantiationException e)
264            {
265                throw new TurbineException("RunData configuration '" + key + "' is illegal", e);
266            }
267    
268            // Set the request and response.
269            data.setRequest(req);
270            data.setResponse(res);
271            // also copy data directly into pipelineData
272            pipelineDataMap.put(HttpServletRequest.class, req);
273            pipelineDataMap.put(HttpServletResponse.class, res);
274    
275            // Set the servlet configuration.
276            data.setServletConfig(config);
277            // also copy data directly into pipelineData
278            pipelineDataMap.put(ServletConfig.class, config);
279    
280            // Set the ServerData.
281            ServerData sd = new ServerData(req);
282            data.setServerData(sd);
283            // also copy data directly into pipelineData
284            pipelineDataMap.put(ServerData.class, sd);
285    
286            // finally put the pipelineDataMap into the pipelineData object
287            data.put(Turbine.class, pipelineDataMap);
288            return data;
289        }
290    
291        /**
292         * Puts the used RunData object back to the factory for recycling.
293         *
294         * @param data the used RunData object.
295         * @return true, if pooling is supported and the object was accepted.
296         */
297        public boolean putRunData(RunData data)
298        {
299            if (data instanceof TurbineRunData)
300            {
301                parserService.putParser(((TurbineRunData) data).getParameterParser());
302                parserService.putParser(((TurbineRunData) data).getCookieParser());
303    
304                return pool.putInstance(data);
305            }
306            else
307            {
308                return false;
309            }
310        }
311    }