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}