001 package com.khubla.pragmatach.framework.router; 002 003 import java.lang.reflect.Method; 004 import java.util.LinkedHashMap; 005 import java.util.Map; 006 import java.util.Map.Entry; 007 008 import org.apache.commons.beanutils.BeanUtils; 009 import org.apache.commons.beanutils.ConvertUtils; 010 import org.slf4j.Logger; 011 import org.slf4j.LoggerFactory; 012 013 import com.khubla.pragmatach.framework.annotation.Controller; 014 import com.khubla.pragmatach.framework.annotation.Route; 015 import com.khubla.pragmatach.framework.annotation.RouteParameter; 016 import com.khubla.pragmatach.framework.api.PragmatachException; 017 import com.khubla.pragmatach.framework.api.Request; 018 import com.khubla.pragmatach.framework.api.Response; 019 import com.khubla.pragmatach.framework.application.Application; 020 import com.khubla.pragmatach.framework.cache.LRUCache; 021 import com.khubla.pragmatach.framework.controller.BeanBoundController; 022 import com.khubla.pragmatach.framework.controller.PragmatachController; 023 import com.khubla.pragmatach.framework.controller.SessionScopedControllers; 024 import com.khubla.pragmatach.framework.controller.impl.NotFoundController; 025 import com.khubla.pragmatach.framework.lifecycle.LifecycleListener; 026 027 /** 028 * @author tome 029 */ 030 public class Router { 031 /** 032 * logger 033 */ 034 private final Logger logger = LoggerFactory.getLogger(this.getClass()); 035 /** 036 * route cache. caches the top 100 routes. 037 */ 038 private static final LRUCache<String, RouteFinder> routeCache = new LRUCache<String, RouteFinder>( 039 getRouteCacheSize()); 040 /** 041 * default size of the route cache 042 */ 043 private static final int DEFAULT_ROUTE_CACHE_SIZE = 100; 044 045 public static LRUCache<String, RouteFinder> getRoutecache() { 046 return routeCache; 047 } 048 049 private static int getRouteCacheSize() { 050 try { 051 int ret = DEFAULT_ROUTE_CACHE_SIZE; 052 final String configuredSize = Application.getConfiguration() 053 .getParameter("pragmatach.routecache.size"); 054 if (null != configuredSize) { 055 ret = Integer.parseInt(configuredSize); 056 } 057 return ret; 058 } catch (final Exception e) { 059 return DEFAULT_ROUTE_CACHE_SIZE; 060 } 061 } 062 063 /** 064 * ctor 065 */ 066 public Router() { 067 } 068 069 /** 070 * find a route, either by matching, or by the cache 071 */ 072 private RouteFinder findRoute(Request request) throws PragmatachException { 073 try { 074 /* 075 * route is cached? 076 */ 077 RouteFinder routeFinder = routeCache.get(request.getURI()); 078 if (null != routeFinder) { 079 return routeFinder; 080 } else { 081 /* 082 * try to find a route 083 */ 084 routeFinder = new RouteFinder(); 085 if (true == routeFinder.match(request)) { 086 routeCache.put(request.getURI(), routeFinder); 087 return routeFinder; 088 } else { 089 return null; 090 } 091 } 092 } catch (final Exception e) { 093 throw new PragmatachException("Exception in findRoute", e); 094 } 095 } 096 097 /** 098 * get instance of Pragmatawch controller. Either a new request controller 099 * or an existing session controller 100 */ 101 private PragmatachController getPragmatachControllerInstance( 102 PragmatachRoute pragmatachRoute, Request request) 103 throws PragmatachException { 104 try { 105 final Class<?> clazz = pragmatachRoute.getMethod() 106 .getDeclaringClass(); 107 final Controller controller = clazz.getAnnotation(Controller.class); 108 if (null != controller) { 109 if (controller.scope() == Controller.Scope.request) { 110 /* 111 * request scope 112 */ 113 return pragmatachRoute.getControllerClazzInstance(request); 114 } else { 115 /* 116 * session scope 117 */ 118 PragmatachController pragmatachController = SessionScopedControllers 119 .getController(request.getSession(), clazz); 120 if (null == pragmatachController) { 121 pragmatachController = pragmatachRoute 122 .getControllerClazzInstance(request); 123 SessionScopedControllers.setController( 124 request.getSession(), pragmatachController); 125 } 126 return pragmatachController; 127 } 128 } else { 129 throw new PragmatachException("Class '" + clazz 130 + "' does not have an @Controller annotation"); 131 } 132 } catch (final Exception e) { 133 throw new PragmatachException( 134 "Exception in getPragmatachControllerInstance", e); 135 } 136 } 137 138 /** 139 * let any lifecycle listeners know we started a request 140 */ 141 private void informLifecycleListenersOfBeginRequest( 142 PragmatachRoute pragmatachRoute, Request request) { 143 for (final LifecycleListener lifecycleListener : Application 144 .getLifecyclelisteners().getLifecycleListeners()) { 145 lifecycleListener.beginRequest(pragmatachRoute, request); 146 } 147 } 148 149 /** 150 * let any lifecycle listeners know we ended a request 151 */ 152 private void informLifecycleListenersOfEndRequest( 153 PragmatachRoute pragmatachRoute, Request request) { 154 for (final LifecycleListener lifecycleListener : Application 155 .getLifecyclelisteners().getLifecycleListeners()) { 156 lifecycleListener.endRequest(pragmatachRoute, request); 157 } 158 } 159 160 /** 161 * invoke a request on a route 162 */ 163 private Response invoke(PragmatachRoute pragmatachRoute, Request request, 164 LinkedHashMap<String, String> parameterMap) 165 throws PragmatachException { 166 try { 167 /* 168 * ret 169 */ 170 Response ret = null; 171 /* 172 * get the controller 173 */ 174 final PragmatachController pragmatachController = getPragmatachControllerInstance( 175 pragmatachRoute, request); 176 /* 177 * set the route 178 */ 179 pragmatachController.setPragmatachRoute(pragmatachRoute); 180 /* 181 * set the request 182 */ 183 pragmatachController.setRequest(request); 184 /* 185 * set fields based on URL parameters 186 */ 187 processParameterData(request, pragmatachController); 188 /* 189 * process form data? 190 */ 191 if (request.getMethod() == Route.HttpMethod.post) { 192 processFormData(pragmatachController); 193 } 194 /* 195 * call the before methods 196 */ 197 for (final Method beforeMethod : pragmatachRoute.getBeforeMethods()) { 198 beforeMethod.invoke(pragmatachController, (Object[]) null); 199 } 200 /* 201 * method and types 202 */ 203 final Method method = pragmatachRoute.getMethod(); 204 final Class<?>[] methodParameterTypes = pragmatachRoute.getMethod() 205 .getParameterTypes(); 206 /* 207 * wildcard? 208 */ 209 if (false == pragmatachRoute.isWildcardRoute()) { 210 if ((null == methodParameterTypes) 211 || (methodParameterTypes.length == 0)) { 212 /* 213 * method takes no parameters 214 */ 215 ret = (Response) method.invoke(pragmatachController); 216 } else { 217 /* 218 * parameters to pass 219 */ 220 final Object[] params = new Object[methodParameterTypes.length]; 221 /* 222 * walk the annotations 223 */ 224 int i = 0; 225 for (final RouteParameter routeParameter : pragmatachRoute 226 .getBoundRouteParameters()) { 227 /* 228 * get the name to bind 229 */ 230 final String boundName = routeParameter.name(); 231 /* 232 * that name is there? 233 */ 234 if (parameterMap.containsKey(boundName)) { 235 /* 236 * set the value in the array 237 */ 238 final String parameterValue = parameterMap 239 .get(boundName); 240 params[i] = ConvertUtils.convert(parameterValue, 241 methodParameterTypes[i]); 242 } 243 i++; 244 } 245 /* 246 * invoke the method 247 */ 248 ret = (Response) method 249 .invoke(pragmatachController, params); 250 } 251 } else { 252 /* 253 * parameters to pass 254 */ 255 final Object[] params = new Object[1]; 256 final String[] lst = new String[parameterMap.size()]; 257 int i = 0; 258 for (final String p : parameterMap.keySet()) { 259 lst[i++] = p; 260 } 261 params[0] = lst; 262 /* 263 * invoke the method 264 */ 265 ret = (Response) method.invoke(pragmatachController, params); 266 } 267 /* 268 * call the after methods 269 */ 270 for (final Method afterMethod : pragmatachRoute.getAfterMethods()) { 271 afterMethod.invoke(pragmatachController, (Object[]) null); 272 } 273 /* 274 * done 275 */ 276 return ret; 277 } catch (final Exception e) { 278 throw new PragmatachException("Exception in invoke", e); 279 } 280 } 281 282 /** 283 * set the controller fields based on the post 284 */ 285 private void processFormData(PragmatachController pragmatachController) 286 throws PragmatachException { 287 try { 288 /* 289 * walk the fields 290 */ 291 if (pragmatachController instanceof BeanBoundController) { 292 final BeanBoundController beanBoundController = (BeanBoundController) pragmatachController; 293 beanBoundController.populateController(); 294 } 295 } catch (final Exception e) { 296 throw new PragmatachException("Exception in processFormData", e); 297 } 298 } 299 300 /** 301 * set the controller fields based on the URL parameters 302 */ 303 private void processParameterData(Request request, 304 PragmatachController pragmatachController) 305 throws PragmatachException { 306 try { 307 /* 308 * walk the fields 309 */ 310 final Map<String, String[]> parameterValues = request 311 .getParameters(); 312 for (final Entry<String, String[]> entry : parameterValues 313 .entrySet()) { 314 /* 315 * set the fields 316 */ 317 BeanUtils.setProperty(pragmatachController, entry.getKey(), 318 entry.getValue()); 319 } 320 } catch (final Exception e) { 321 throw new PragmatachException("Exception in processFormData", e); 322 } 323 } 324 325 /** 326 * route request 327 */ 328 public Response route(Request request) throws PragmatachException { 329 try { 330 /* 331 * get route 332 */ 333 final RouteFinder routeFinder = findRoute(request); 334 if (null != routeFinder) { 335 /* 336 * note a message 337 */ 338 logger.debug(request.getMethod() + " request for: " 339 + request.getURI() + " routed to " 340 + routeFinder.getPragmatachRoute().getDescription()); 341 /* 342 * inform lifecycle listeners of start of request 343 */ 344 informLifecycleListenersOfBeginRequest( 345 routeFinder.getPragmatachRoute(), request); 346 /* 347 * invoke 348 */ 349 try { 350 return invoke(routeFinder.getPragmatachRoute(), request, 351 routeFinder.getParameterMap()); 352 } catch (final Exception e) { 353 throw e; 354 } finally { 355 /* 356 * inform lifecycle listeners of end of request 357 */ 358 informLifecycleListenersOfEndRequest( 359 routeFinder.getPragmatachRoute(), request); 360 } 361 } else { 362 /* 363 * log a message 364 */ 365 logger.info(request.getMethod() + " request for: " 366 + request.getURI() + " could not be routed"); 367 /* 368 * no match, return 404 369 */ 370 final NotFoundController notFoundController = new NotFoundController(); 371 notFoundController.setRequest(request); 372 return notFoundController.render(); 373 } 374 } catch (final Exception e) { 375 throw new PragmatachException("Exception in getRoute", e); 376 } 377 } 378 }