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    }