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 }