Package org.springmodules.xt.ajax

Source Code of org.springmodules.xt.ajax.AjaxInterceptor

/*
* Copyright 2006 - 2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springmodules.xt.ajax;

import java.io.IOException;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import net.sf.json.JSONObject;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
import org.springmodules.xt.ajax.support.IllegalViewException;
import org.springmodules.xt.ajax.support.NoMatchingHandlerException;
import org.springmodules.xt.ajax.support.UnsupportedEventException;
import org.springmodules.xt.ajax.action.RedirectAction;
import org.springmodules.xt.ajax.util.AjaxResponseSender;

/**
* <p>Spring web interceptor which intercepts http requests and handles ajax requests.<br>
* Ajax requests are identified by a particular request parameter, by default named "ajax-request": it can assume two different values, depending on the type of ajax request:
* an "action request", causing no form submission, and a "submit request", causing form submission.</p>
*
* <p>This interceptor delegates ajax requests handling to {@link AjaxHandler}s configured via handler mappings
* (see {@link #setHandlerMappings(Properties)}).</p>
*
* <p>Configured mappings are a {@link java.util.Properties} file / object where each entry associates an ANT based URL path with a comma separated list of
* {@link AjaxHandler}s configured in the Spring application context; when associating the same URL pattern with multiple handlers,
* all handlers will be merged.</p>
*
* <p>When the interceptor receives an ajax request, it looks its mappings for an appropriate set of handlers: then, each handler will be evaluated following
* the longest path match order, that is, an handler configured in the path "/test" will be evaluated prior to an handler configured in the path "/*".</p>
*
* <p>The first handler supporting the ajax event associated with the request will be executed.<br>
* If the same URL is associated with more than one handler, the one supporting the current event will be executed.</p>
*
* <p>Note that if more handlers support the same event, the one configured for matching the longest path will be executed (in the example above,
* the one configured for the path "/test"): this is useful for overriding event handlers.</p>
*
* @author Sergio Bossa
*/
public class AjaxInterceptor extends HandlerInterceptorAdapter implements ApplicationContextAware {
   
    public static final String AJAX_ACTION_REQUEST = "ajax-action";
    public static final String AJAX_SUBMIT_REQUEST = "ajax-submit";
   
    public static final String AJAX_REDIRECT_PREFIX = "ajax-redirect:";
    public static final String STANDARD_REDIRECT_PREFIX = "redirect:";
   
    private static final String MODEL_KEY = AjaxInterceptor.class.getName() + ".MODEL_KEY";
   
    private static final Logger logger = Logger.getLogger(AjaxInterceptor.class);
   
    private UrlPathHelper urlPathHelper = new UrlPathHelper();
    private PathMatcher pathMatcher = new AntPathMatcher();
   
    private String ajaxParameter = "ajax-request";
    private String eventParameter = "event-id";
    private String elementParameter = "source-element";
    private String elementIdParameter = "source-element-id";
    private String jsonParamsParameter = "json-params";
   
    private FormDataAccessor formDataAccessor = new MVCFormDataAccessor();
    private MultiMap handlerMappings = new MultiValueMap();
   
    private ApplicationContext applicationContext;
   
    /**
     * Pre-handle the http request and if this is an ajax request firing an action, looks for a mapped ajax handler, executes it and
     * returns an ajax response.<br>
     * Important: if the matching mapped handler returns a <b>null</b> or empty ajax response, the interceptor <b>does not proceed</b> with the execution chain.
     *
     * @throws UnsupportedEventException If the event associated with this ajax request is not supported by any
     * mapped handler.
     * @throws EventHandlingException If an error occurred during event handling.
     * @throws NoMatchingHandlerException If no mapped handler matching the URL can be found.
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
        if (WebUtils.isIncludeRequest(request)) {
            return true;
        }
        try {
            String requestType = request.getParameter(this.ajaxParameter);
            if (requestType != null && requestType.equals(AJAX_ACTION_REQUEST)) {
                String eventId = request.getParameter(this.eventParameter);
               
                if (eventId == null) {
                    throw new IllegalStateException("Event id cannot be null.");
                }
               
                logger.info(new StringBuilder("Pre-handling ajax request for event: ").append(eventId));
               
                List<AjaxHandler> handlers = this.lookupHandlers(request);
                if (handlers.isEmpty()) {
                    throw new NoMatchingHandlerException("Cannot find an handler matching the request: " +
                            this.urlPathHelper.getLookupPathForRequest(request));
                } else {
                    AjaxActionEvent event = new AjaxActionEventImpl(eventId, request);
                    AjaxResponse ajaxResponse = null;
                    boolean supported = false;
                    for (AjaxHandler ajaxHandler : handlers) {
                        if (ajaxHandler.supports(event)) {
                            // Set event properties:
                            this.initEvent(event, request);
                            Map model = this.getModel(request.getSession());
                            if (model != null) {
                                Object commandObject = this.formDataAccessor.getCommandObject(request, response, handler, model);
                                event.setCommandObject(commandObject);
                            }
                            // Handle event:
                            ajaxResponse = ajaxHandler.handle(event);
                            supported = true;
                            break;
                        }
                    }
                    if (!supported) {
                        throw new UnsupportedEventException("Cannot handling the given event with id: " + eventId);
                    } else {
                        if (ajaxResponse != null && !ajaxResponse.isEmpty()) {
                            logger.info("Sending Ajax response after Ajax action.");
                            AjaxResponseSender.sendResponse(response, ajaxResponse);
                        } else {
                            AjaxResponseSender.sendResponse(response, new AjaxResponseImpl());
                        }
                        return false;
                    }
                }
            } else {
                return true;
            }
        } catch(Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw ex;
        }
    }
   
    /**
     * Post-handle the http request and if it was an ajax request firing a submit, looks for a mapped ajax handler, executes it and
     * returns an ajax response.<br>
     * If the matching mapped handler returns a null or empty ajax response, the interceptor looks for a view configured with
     * the {@link #AJAX_REDIRECT_PREFIX} and make a redirect to it; if the configured view has no
     * {@link #AJAX_REDIRECT_PREFIX}, an exception is thrown.
     *
     * @throws UnsupportedEventException If the event associated with this ajax request is not supported by any
     * mapped handler.
     * @throws EventHandlingException If an error occurred during event handling.
     * @throws NoMatchingHandlerException If no mapped handler matching the URL can be found.
     * @throws IllegalViewException If the view to which redirect to doesn't contain the {@link #AJAX_REDIRECT_PREFIX}.
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    throws Exception {
        if (WebUtils.isIncludeRequest(request)) {
            return;
        }
        try {
            // If modelAndView object is null, it means that the controller handled the request by itself ...
            // See : http://static.springframework.org/spring/docs/2.0.x/api/org/springframework/web/servlet/mvc/Controller.html#handleRequest(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse)
            if (modelAndView == null) {
                logger.info("Null ModelAndView object, proceeding without Ajax processing ...");
                return;
            }
            //
            // Store the model map:
            this.storeModel(request.getSession(), modelAndView.getModel());
            //
            // Continue processing:
            //
            String requestType = request.getParameter(this.ajaxParameter);
            if (requestType != null && requestType.equals(AJAX_SUBMIT_REQUEST)) {
                String eventId = request.getParameter(this.eventParameter);
               
                if (eventId == null) {
                    throw new IllegalStateException("Event id cannot be null.");
                }
               
                logger.info(new StringBuilder("Post-handling ajax request for event: ").append(eventId));
               
                List<AjaxHandler> handlers = this.lookupHandlers(request);
                if (handlers.isEmpty()) {
                    throw new NoMatchingHandlerException("Cannot find an handler matching the request: " +
                            this.urlPathHelper.getLookupPathForRequest(request));
                } else {
                    AjaxSubmitEvent event = new AjaxSubmitEventImpl(eventId, request);
                    AjaxResponse ajaxResponse = null;
                    boolean supported = false;
                    for (AjaxHandler ajaxHandler : handlers) {
                        if (ajaxHandler.supports(event)) {
                            // Set event properties:
                            this.initEvent(event, request);
                            Map model = this.getModel(request.getSession());
                            if (model != null) {
                                Object commandObject = this.formDataAccessor.getCommandObject(request, response, handler, model);
                                Errors errors = this.formDataAccessor.getValidationErrors(request, response, handler, model);
                                event.setCommandObject(commandObject);
                                event.setValidationErrors(errors);
                                event.setModel(model);
                            }
                            // Handle event:
                            ajaxResponse = ajaxHandler.handle(event);
                            supported = true;
                            break;
                        }
                    }
                    if (!supported) {
                        throw new UnsupportedEventException("Cannot handling the given event with id: " + eventId);
                    } else {
                        if (ajaxResponse != null && ! ajaxResponse.isEmpty()) {
                            // Need to clear the ModelAndView because we are handling the response by ourselves:
                            modelAndView.clear();
                            AjaxResponseSender.sendResponse(response, ajaxResponse);
                        } else {
                            // No response, so try an Ajax redirect:
                            String view = modelAndView.getViewName();
                            if (view != null && view.startsWith(AJAX_REDIRECT_PREFIX)) {
                                view = view.substring(AJAX_REDIRECT_PREFIX.length());
                                this.redirectToView(view, request, response, modelAndView);
                            } else if (view != null && view.startsWith(STANDARD_REDIRECT_PREFIX)) {
                                view = view.substring(STANDARD_REDIRECT_PREFIX.length());
                                this.redirectToView(view, request, response, modelAndView);
                            } else {
                                throw new IllegalViewException("No Ajax redirect prefix: " + AJAX_REDIRECT_PREFIX + " found for view: " + view);
                            }
                           
                        }
                    }
                }
            }
        } catch(Exception ex) {
            logger.error(ex.getMessage(), ex);
            throw ex;
        }
    }
   
    /**
     * Return true if the given request is an Ajax one, false otherwise.
     */
    public boolean isAjaxRequest(HttpServletRequest request) {
        String requestType = request.getParameter(this.ajaxParameter);
        if (requestType != null && (requestType.equals(AJAX_ACTION_REQUEST) || requestType.equals(AJAX_SUBMIT_REQUEST))) {
            return true;
        } else {
            return false;
        }
    }
   
    /**
     * Set the {@link FormDataAccessor} to use for getting form data to put in {@link AjaxEvent} objects.<br>
     * By default it uses a {@link MVCFormDataAccessor}, in order to access form data in Spring MVC environments.<br>
     * Set another accessor implementation for accessing form data in other environments, like Spring Web Flow.
     *
     * @param accessor The {@link FormDataAccessor}.
     */
    public void setFormDataAccessor(FormDataAccessor accessor) {
        this.formDataAccessor = accessor;
    }
   
    /**
     * Set mappings configured in the given {@link java.util.Properties} object.<br>
     * Each mapping associates an ANT based URL path with a comma separated list of {@link AjaxHandler}s configured in the Spring Application Context.<br>
     * Mappings are ordered in a sorted map, following the longest path order (from the longest path to the shorter).<br>
     * Please note that multiple mappings to the same URL are supported thanks to a {@link org.apache.commons.collections.map.MultiValueMap}.
     *
     * @param mappings A {@link java.util.Properties} containing handler mappings.
     */
    public void setHandlerMappings(Properties mappings) {
        this.handlerMappings = MultiValueMap.decorate(new TreeMap<String, String>(new Comparator() {
            public int compare(Object o1, Object o2) {
                if (!(o1 instanceof String) || !(o2 instanceof String)) {
                    throw new ClassCastException("You have to map an URL to a comma separated list of handler names.");
                }
                if (o1.equals(o2)) {
                    return 0;
                } else if (o1.toString().length() > o2.toString().length()) {
                    return -1;
                } else {
                    return 1;
                }
            }
        }));
       
        for (Map.Entry entry : mappings.entrySet()) {
            String[] handlers = ((String) entry.getValue()).split(",");
            for (String handler : handlers) {
                String url = (String) entry.getKey();
                if (! url.startsWith("/")) {
                    url = "/" + url;
                }
                this.handlerMappings.put(url.trim(), handler.trim());
            }
        }
    }
   
    public void setAjaxParameter(String ajaxParameter) {
        this.ajaxParameter = ajaxParameter;
    }
   
    public void setElementParameter(String elementParameter) {
        this.elementParameter = elementParameter;
    }
   
    public void setElementIdParameter(String elementIdParameter) {
        this.elementIdParameter = elementIdParameter;
    }
   
    public void setEventParameter(String eventParameter) {
        this.eventParameter = eventParameter;
    }
   
    public void setJsonParamsParameter(String jsonParamsParameter) {
        this.jsonParamsParameter = jsonParamsParameter;
    }
   
    public String getAjaxParameter() {
        return this.ajaxParameter;
    }
   
    public String getElementParameter() {
        return this.elementParameter;
    }
   
    public String getElementIdParameter() {
        return this.elementIdParameter;
    }
   
    public String getEventParameter() {
        return this.eventParameter;
    }
   
    public String getJsonParamsParameter() {
        return this.jsonParamsParameter;
    }
   
    public void setApplicationContext(ApplicationContext applicationContext)
    throws BeansException {
        this.applicationContext = applicationContext;
    }
   
    /*** Protected stuff ***/
   
    /**
     * Look up ajax handlers associated with the URL path of the given request.
     * <p>Supports direct matches, e.g.  "/test" matches "/test",
     * and various Ant-style pattern matches, e.g. "/t*" matches
     * both "/test" and "/team".</p>
     * <p>Remember that multiple matches are merged: e.g., if both "/test" and "/t*" match,
     * mappings will be merged and evaluated following longest path order.</p>
     * <p>Moreover, remember that multiple handlers can be mapped to the same URL.</p>
     *
     * @param request The current http request.
     * @return A {@link java.util.List} of {@link AjaxHandler}s associated with the URL of the given request; if no matching is found,
     * an empty list is returned.
     */
    protected List<AjaxHandler> lookupHandlers(HttpServletRequest request) {
        String urlPath = this.urlPathHelper.getLookupPathForRequest(request);
        List<AjaxHandler> handlers = new LinkedList<AjaxHandler>();
        for (Map.Entry entry : (Set<Map.Entry>) this.handlerMappings.entrySet()) {
            String configuredPath = (String) entry.getKey();
            if (this.pathMatcher.match(configuredPath, urlPath)) {
                Collection handlerNames = (Collection) entry.getValue();
                for (Object handlerName : handlerNames) {
                    AjaxHandler current = (AjaxHandler) this.applicationContext.getBean((String) handlerName);
                    if (current != null) {
                        handlers.add(current);
                    } else {
                        logger.warn(new StringBuilder("Non-existent handler ").append(handlerName).append(" mapped at ").append(configuredPath));
                    }
                }
            }
        }
        return handlers;
    }
   
    /**
     * Store the model map for later access and usage.
     */
    protected void storeModel(HttpSession session, Map model) {
        session.setAttribute(MODEL_KEY, model);
    }
   
    /**
     * Get the model map.
     */
    protected Map getModel(HttpSession session) {
        Map model = (Map) session.getAttribute(MODEL_KEY);
        if (model == null) {
            logger.warn("Null model map for session: " + session.getId());
        }
        return model;
    }
   
    /*** Private class internals ***/
   
    private void initEvent(AjaxEvent event, HttpServletRequest request) {
        String paramsString = request.getParameter(this.jsonParamsParameter);
        if (paramsString != null) {
            Map<String, String> parameters = new HashMap<String, String>();
            JSONObject json = JSONObject.fromString(paramsString);
            Iterator keys = json.keys();
            while (keys.hasNext()) {
                String key = keys.next().toString();
                parameters.put(key, json.opt(key).toString());
            }
            event.setParameters(parameters);
        }
       
        event.setElementName(request.getParameter(this.elementParameter));
        event.setElementId(request.getParameter(this.elementIdParameter));
    }
   
    private void redirectToView(String view, HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) throws IOException {
        // Creating Ajax redirect action:
        AjaxResponse ajaxResponse = new AjaxResponseImpl();
        AjaxAction ajaxAction = new RedirectAction(new StringBuilder(request.getContextPath()).append(view).toString(), modelAndView);
        ajaxResponse.addAction(ajaxAction);
        // Need to clear the ModelAndView because we are handling the response by ourselves:
        modelAndView.clear();
        AjaxResponseSender.sendResponse(response, ajaxResponse);
    }
}
TOP

Related Classes of org.springmodules.xt.ajax.AjaxInterceptor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.