Open Source Repository

Home /spring/spring-web-portlet-3.0.5 | Repository Home



org/springframework/web/portlet/mvc/annotation/AnnotationMethodHandlerAdapter.java
/*
 * Copyright 2002-2010 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.springframework.web.portlet.mvc.annotation;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.ClientDataRequest;
import javax.portlet.Event;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.MimeResponse;
import javax.portlet.PortalContext;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.portlet.StateAwareResponse;
import javax.portlet.WindowState;
import javax.servlet.http.Cookie;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanExpressionContext;
import org.springframework.beans.factory.config.BeanExpressionResolver;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.Ordered;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.support.BindingAwareModelMap;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.annotation.support.HandlerMethodInvoker;
import org.springframework.web.bind.annotation.support.HandlerMethodResolver;
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
import org.springframework.web.bind.support.SessionAttributeStore;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.portlet.HandlerAdapter;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.NoHandlerFoundException;
import org.springframework.web.portlet.bind.MissingPortletRequestParameterException;
import org.springframework.web.portlet.bind.PortletRequestDataBinder;
import org.springframework.web.portlet.bind.annotation.ActionMapping;
import org.springframework.web.portlet.bind.annotation.EventMapping;
import org.springframework.web.portlet.bind.annotation.RenderMapping;
import org.springframework.web.portlet.bind.annotation.ResourceMapping;
import org.springframework.web.portlet.context.PortletWebRequest;
import org.springframework.web.portlet.handler.PortletContentGenerator;
import org.springframework.web.portlet.handler.PortletSessionRequiredException;
import org.springframework.web.portlet.util.PortletUtils;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;

/**
 * Implementation of the {@link org.springframework.web.portlet.HandlerAdapter}
 * interface that maps handler methods based on portlet modes, action/render phases
 * and request parameters expressed through the {@link RequestMapping} annotation.
 *
 <p>Supports request parameter binding through the {@link RequestParam} annotation.
 * Also supports the {@link ModelAttribute} annotation for exposing model attribute
 * values to the view, as well as {@link InitBinder} for binder initialization methods
 * and {@link SessionAttributes} for automatic session management of specific attributes.
 *
 <p>This adapter can be customized through various bean properties.
 * A common use case is to apply shared binder initialization logic through
 * a custom {@link #setWebBindingInitializer WebBindingInitializer}.
 *
 @author Juergen Hoeller
 @author Arjen Poutsma
 @since 2.5
 @see #setWebBindingInitializer
 @see #setSessionAttributeStore
 */
public class AnnotationMethodHandlerAdapter extends PortletContentGenerator
    implements HandlerAdapter, Ordered, BeanFactoryAware {

  public static final String IMPLICIT_MODEL_SESSION_ATTRIBUTE =
      AnnotationMethodHandlerAdapter.class.getName() ".IMPLICIT_MODEL";

  public static final String IMPLICIT_MODEL_RENDER_PARAMETER = "implicitModel";


  private WebBindingInitializer webBindingInitializer;

  private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();

  private int cacheSecondsForSessionAttributeHandlers = 0;

  private boolean synchronizeOnSession = false;

  private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

  private WebArgumentResolver[] customArgumentResolvers;

  private ModelAndViewResolver[] customModelAndViewResolvers;

  private int order = Ordered.LOWEST_PRECEDENCE;

  private ConfigurableBeanFactory beanFactory;

  private BeanExpressionContext expressionContext;

  private final Map<Class<?>, PortletHandlerMethodResolver> methodResolverCache =
      new HashMap<Class<?>, PortletHandlerMethodResolver>();


  /**
   * Specify a WebBindingInitializer which will apply pre-configured
   * configuration to every DataBinder that this controller uses.
   */
  public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
    this.webBindingInitializer = webBindingInitializer;
  }

  /**
   * Specify the strategy to store session attributes with.
   <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
   * storing session attributes in the PortletSession, using the same
   * attribute name as in the model.
   */
  public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
    Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
    this.sessionAttributeStore = sessionAttributeStore;
  }

  /**
   * Cache content produced by <code>@SessionAttributes</code> annotated handlers
   * for the given number of seconds. Default is 0, preventing caching completely.
   <p>In contrast to the "cacheSeconds" property which will apply to all general
   * handlers (but not to <code>@SessionAttributes</code> annotated handlers), this
   * setting will apply to <code>@SessionAttributes</code> annotated handlers only.
   @see #setCacheSeconds
   @see org.springframework.web.bind.annotation.SessionAttributes
   */
  public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
    this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
  }

  /**
   * Set if controller execution should be synchronized on the session,
   * to serialize parallel invocations from the same client.
   <p>More specifically, the execution of each handler method will get
   * synchronized if this flag is "true". The best available session mutex
   * will be used for the synchronization; ideally, this will be a mutex
   * exposed by HttpSessionMutexListener.
   <p>The session mutex is guaranteed to be the same object during
   * the entire lifetime of the session, available under the key defined
   * by the <code>SESSION_MUTEX_ATTRIBUTE</code> constant. It serves as a
   * safe reference to synchronize on for locking on the current session.
   <p>In many cases, the PortletSession reference itself is a safe mutex
   * as well, since it will always be the same object reference for the
   * same active logical session. However, this is not guaranteed across
   * different servlet containers; the only 100% safe way is a session mutex.
   @see org.springframework.web.util.HttpSessionMutexListener
   @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession)
   */
  public void setSynchronizeOnSession(boolean synchronizeOnSession) {
    this.synchronizeOnSession = synchronizeOnSession;
  }

  /**
   * Set the ParameterNameDiscoverer to use for resolving method parameter
   * names if needed (e.g. for default attribute names).
   <p>Default is a {@link org.springframework.core.LocalVariableTableParameterNameDiscoverer}.
   */
  public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
    this.parameterNameDiscoverer = parameterNameDiscoverer;
  }

  /**
   * Set a custom WebArgumentResolver to use for special method parameter types.
   * Such a custom WebArgumentResolver will kick in first, having a chance to
   * resolve an argument value before the standard argument handling kicks in.
   */
  public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
    this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
  }

  /**
   * Set one or more custom WebArgumentResolvers to use for special method
   * parameter types. Any such custom WebArgumentResolver will kick in first,
   * having a chance to resolve an argument value before the standard
   * argument handling kicks in.
   */
  public void setCustomArgumentResolvers(WebArgumentResolver[] argumentResolvers) {
    this.customArgumentResolvers = argumentResolvers;
  }

  /**
   * Set a custom ModelAndViewResolvers to use for special method return types. Such a custom ModelAndViewResolver will kick
   * in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
   */
  public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
    this.customModelAndViewResolvers = new ModelAndViewResolver[]{customModelAndViewResolver};
  }

  /**
   * Set one or more custom ModelAndViewResolvers to use for special method return types. Any such custom ModelAndViewResolver
   * will kick in first, having a chance to resolve an return value before the standard ModelAndView handling kicks in.
   */
  public void setCustomModelAndViewResolvers(ModelAndViewResolver[] customModelAndViewResolvers) {
    this.customModelAndViewResolvers = customModelAndViewResolvers;
  }

  /**
   * Specify the order value for this HandlerAdapter bean.
   <p>Default value is <code>Integer.MAX_VALUE</code>, meaning that it's non-ordered.
   @see org.springframework.core.Ordered#getOrder()
   */
  public void setOrder(int order) {
    this.order = order;
  }

  public int getOrder() {
    return this.order;
  }

  public void setBeanFactory(BeanFactory beanFactory) {
    if (beanFactory instanceof ConfigurableBeanFactory) {
      this.beanFactory = (ConfigurableBeanFactorybeanFactory;
      this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
    }
  }


  public boolean supports(Object handler) {
    return getMethodResolver(handler).hasHandlerMethods();
  }

  public void handleAction(ActionRequest request, ActionResponse response, Object handlerthrows Exception {
    Object returnValue = doHandle(request, response, handler);
    if (returnValue != null) {
      throw new IllegalStateException("Invalid action method return value: " + returnValue);
    }
  }

  public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handlerthrows Exception {
    checkAndPrepare(request, response);
    return doHandle(request, response, handler);
  }

  public ModelAndView handleResource(ResourceRequest request, ResourceResponse response, Object handlerthrows Exception {
    checkAndPrepare(request, response);
    return doHandle(request, response, handler);
  }

  public void handleEvent(EventRequest request, EventResponse response, Object handlerthrows Exception {
    Object returnValue = doHandle(request, response, handler);
    if (returnValue != null) {
      throw new IllegalStateException("Invalid event method return value: " + returnValue);
    }
  }

  protected ModelAndView doHandle(PortletRequest request, PortletResponse response, Object handlerthrows Exception {
    ExtendedModelMap implicitModel = null;

    if (response instanceof MimeResponse) {
      MimeResponse mimeResponse = (MimeResponseresponse;
      // Detect implicit model from associated action phase.
      if (response instanceof RenderResponse) {
        PortletSession session = request.getPortletSession(false);
        if (session != null) {
          if (request.getParameter(IMPLICIT_MODEL_RENDER_PARAMETER!= null) {
            implicitModel = (ExtendedModelMapsession.getAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
          }
          else {
            session.removeAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
          }
        }
      }
      if (handler.getClass().getAnnotation(SessionAttributes.class!= null) {
        // Always prevent caching in case of session attribute management.
        checkAndPrepare(request, mimeResponse, this.cacheSecondsForSessionAttributeHandlers);
      }
      else {
        // Uses configured default cacheSeconds setting.
        checkAndPrepare(request, mimeResponse);
      }
    }

    if (implicitModel == null) {
      implicitModel = new BindingAwareModelMap();
    }

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
      PortletSession session = request.getPortletSession(false);
      if (session != null) {
        Object mutex = PortletUtils.getSessionMutex(session);
        synchronized (mutex) {
          return invokeHandlerMethod(request, response, handler, implicitModel);
        }
      }
    }

    return invokeHandlerMethod(request, response, handler, implicitModel);
  }

  @SuppressWarnings("unchecked")
  private ModelAndView invokeHandlerMethod(
      PortletRequest request, PortletResponse response, Object handler, ExtendedModelMap implicitModel)
      throws Exception {

    PortletWebRequest webRequest = new PortletWebRequest(request, response);
    PortletHandlerMethodResolver methodResolver = getMethodResolver(handler);
    Method handlerMethod = methodResolver.resolveHandlerMethod(request);
    PortletHandlerMethodInvoker methodInvoker = new PortletHandlerMethodInvoker(methodResolver);

    Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
    ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel,
        webRequest);
    methodInvoker.updateModelAttributes(
        handler, (mav != null ? mav.getModel() null), implicitModel, webRequest);

    // Expose implicit model for subsequent render phase.
    if (response instanceof StateAwareResponse && !implicitModel.isEmpty()) {
      StateAwareResponse stateResponse = (StateAwareResponseresponse;
      Map<?, ?> modelToStore = implicitModel;
      try {
        stateResponse.setRenderParameter(IMPLICIT_MODEL_RENDER_PARAMETER, Boolean.TRUE.toString());
        if (response instanceof EventResponse) {
          // Update the existing model, if any, when responding to an event -
          // whereas we're replacing the model in case of an action response.
          Map existingModel = (Maprequest.getPortletSession().getAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
          if (existingModel != null) {
            existingModel.putAll(implicitModel);
            modelToStore = existingModel;
          }
        }
        request.getPortletSession().setAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE, modelToStore);
      }
      catch (IllegalStateException ex) {
        // Probably sendRedirect called... no need to expose model to render phase.
      }
    }

    return mav;
  }

  /**
   * Build a HandlerMethodResolver for the given handler type.
   */
  private PortletHandlerMethodResolver getMethodResolver(Object handler) {
    Class handlerClass = ClassUtils.getUserClass(handler);
    synchronized (this.methodResolverCache) {
      PortletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
      if (resolver == null) {
        resolver = new PortletHandlerMethodResolver(handlerClass);
        this.methodResolverCache.put(handlerClass, resolver);
      }
      return resolver;
    }
  }


  /**
   * Template method for creating a new PortletRequestDataBinder instance.
   <p>The default implementation creates a standard PortletRequestDataBinder.
   * This can be overridden for custom PortletRequestDataBinder subclasses.
   @param request current portlet request
   @param target the target object to bind onto (or <code>null</code>
   * if the binder is just used to convert a plain parameter value)
   @param objectName the objectName of the target object
   @return the PortletRequestDataBinder instance to use
   @throws Exception in case of invalid state or arguments
   @see PortletRequestDataBinder#bind(javax.portlet.PortletRequest)
   @see PortletRequestDataBinder#convertIfNecessary(Object, Class, MethodParameter)
   */
  protected PortletRequestDataBinder createBinder(
      PortletRequest request, Object target, String objectNamethrows Exception {

    return new PortletRequestDataBinder(target, objectName);
  }


  /**
   * Portlet-specific subclass of {@link HandlerMethodResolver}.
   */
  private static class PortletHandlerMethodResolver extends HandlerMethodResolver {

    private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();

    public PortletHandlerMethodResolver(Class<?> handlerType) {
      init(handlerType);
    }

    @Override
    protected boolean isHandlerMethod(Method method) {
      if (this.mappings.containsKey(method)) {
        return true;
      }
      RequestMappingInfo mappingInfo = new RequestMappingInfo();
      RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
      ActionMapping actionMapping = AnnotationUtils.findAnnotation(method, ActionMapping.class);
      RenderMapping renderMapping = AnnotationUtils.findAnnotation(method, RenderMapping.class);
      ResourceMapping resourceMapping = AnnotationUtils.findAnnotation(method, ResourceMapping.class);
      EventMapping eventMapping = AnnotationUtils.findAnnotation(method, EventMapping.class);
      if (actionMapping != null) {
        mappingInfo.initPhaseMapping(PortletRequest.ACTION_PHASE, actionMapping.value(), actionMapping.params());
      }
      if (renderMapping != null) {
        mappingInfo.initPhaseMapping(PortletRequest.RENDER_PHASE, renderMapping.value(), renderMapping.params());
      }
      if (resourceMapping != null) {
        mappingInfo.initPhaseMapping(PortletRequest.RESOURCE_PHASE, resourceMapping.value()new String[0]);
      }
      if (eventMapping != null) {
        mappingInfo.initPhaseMapping(PortletRequest.EVENT_PHASE, eventMapping.value()new String[0]);
      }
      if (requestMapping != null) {
        mappingInfo.initStandardMapping(requestMapping.value(), requestMapping.method(),
            requestMapping.params(), requestMapping.headers());
        if (mappingInfo.phase == null) {
          mappingInfo.phase = determineDefaultPhase(method);
        }
      }
      if (mappingInfo.phase != null) {
        this.mappings.put(method, mappingInfo);
        return true;
      }
      return false;
    }

    public Method resolveHandlerMethod(PortletRequest requestthrows PortletException {
      Map<RequestMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestMappingInfo, Method>();
      for (Method handlerMethod : getHandlerMethods()) {
        RequestMappingInfo mappingInfo = this.mappings.get(handlerMethod);
        if (mappingInfo.match(request)) {
          Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
          if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
            throw new IllegalStateException("Ambiguous handler methods mapped for portlet mode '" +
                request.getPortletMode() "': {" + oldMappedMethod + ", " + handlerMethod +
                "}. If you intend to handle the same mode in multiple methods, then factor " +
                "them out into a dedicated handler class with that mode mapped at the type level!");
          }
        }
      }
      if (!targetHandlerMethods.isEmpty()) {
        if (targetHandlerMethods.size() == 1) {
          return targetHandlerMethods.values().iterator().next();
        }
        else {
          RequestMappingInfo bestMappingMatch = null;
          for (RequestMappingInfo mapping : targetHandlerMethods.keySet()) {
            if (bestMappingMatch == null) {
              bestMappingMatch = mapping;
            }
            else {
              if (mapping.isBetterMatchThan(bestMappingMatch)) {
                bestMappingMatch = mapping;
              }
            }
          }
          return targetHandlerMethods.get(bestMappingMatch);
        }
      }
      else {
        throw new NoHandlerFoundException("No matching handler method found for portlet request", request);
      }
    }

    private String determineDefaultPhase(Method handlerMethod) {
      if (!void.class.equals(handlerMethod.getReturnType())) {
        return PortletRequest.RENDER_PHASE;
      }
      for (Class<?> argType : handlerMethod.getParameterTypes()) {
        if (ActionRequest.class.isAssignableFrom(argType|| ActionResponse.class.isAssignableFrom(argType||
            InputStream.class.isAssignableFrom(argType|| Reader.class.isAssignableFrom(argType)) {
          return PortletRequest.ACTION_PHASE;
        }
        else if (RenderRequest.class.isAssignableFrom(argType|| RenderResponse.class.isAssignableFrom(argType||
            OutputStream.class.isAssignableFrom(argType|| Writer.class.isAssignableFrom(argType)) {
          return PortletRequest.RENDER_PHASE;
        }
        else if (ResourceRequest.class.isAssignableFrom(argType|| ResourceResponse.class.isAssignableFrom(argType)) {
          return PortletRequest.RESOURCE_PHASE;
        }
        else if (EventRequest.class.isAssignableFrom(argType|| EventResponse.class.isAssignableFrom(argType)) {
          return PortletRequest.EVENT_PHASE;
        }
      }
      return "";
    }
  }


  /**
   * Portlet-specific subclass of {@link HandlerMethodInvoker}.
   */
  private class PortletHandlerMethodInvoker extends HandlerMethodInvoker {

    public PortletHandlerMethodInvoker(HandlerMethodResolver resolver) {
      super(resolver, webBindingInitializer, sessionAttributeStore,
          parameterNameDiscoverer, customArgumentResolvers, null);
    }

    @Override
    protected void raiseMissingParameterException(String paramName, Class paramTypethrows Exception {
      throw new MissingPortletRequestParameterException(paramName, paramType.getSimpleName());
    }

    @Override
    protected void raiseSessionRequiredException(String messagethrows Exception {
      throw new PortletSessionRequiredException(message);
    }

    @Override
    protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
        throws Exception {

      return AnnotationMethodHandlerAdapter.this.createBinder(
          webRequest.getNativeRequest(PortletRequest.class), target, objectName);
    }

    @Override
    protected void doBind(WebDataBinder binder, NativeWebRequest webRequestthrows Exception {
      PortletRequestDataBinder portletBinder = (PortletRequestDataBinderbinder;
      portletBinder.bind(webRequest.getNativeRequest(PortletRequest.class));
    }

    @Override
    protected Object resolveDefaultValue(String value) {
      if (beanFactory == null) {
        return value;
      }
      String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
      BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
      if (exprResolver == null) {
        return value;
      }
      return exprResolver.evaluate(placeholdersResolved, expressionContext);
    }

    @Override
    protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest)
        throws Exception {

      PortletRequest portletRequest = webRequest.getNativeRequest(PortletRequest.class);
      Cookie cookieValue = PortletUtils.getCookie(portletRequest, cookieName);
      if (Cookie.class.isAssignableFrom(paramType)) {
        return cookieValue;
      }
      else if (cookieValue != null) {
        return cookieValue.getValue();
      }
      else {
        return null;
      }
    }

    @Override
    protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest)
        throws Exception {

      PortletRequest request = webRequest.getNativeRequest(PortletRequest.class);
      PortletResponse response = webRequest.getNativeResponse(PortletResponse.class);

      if (PortletRequest.class.isAssignableFrom(parameterType||
          MultipartRequest.class.isAssignableFrom(parameterType)) {
        Object nativeRequest = webRequest.getNativeRequest(parameterType);
        if (nativeRequest == null) {
          throw new IllegalStateException(
              "Current request is not of type [" + parameterType.getName() "]: " + request);
        }
        return nativeRequest;
      }
      else if (PortletResponse.class.isAssignableFrom(parameterType)) {
        Object nativeResponse = webRequest.getNativeResponse(parameterType);
        if (nativeResponse == null) {
          throw new IllegalStateException(
              "Current response is not of type [" + parameterType.getName() "]: " + response);
        }
        return nativeResponse;
      }
      else if (PortletSession.class.isAssignableFrom(parameterType)) {
        return request.getPortletSession();
      }
      else if (PortletPreferences.class.isAssignableFrom(parameterType)) {
        return request.getPreferences();
      }
      else if (PortletMode.class.isAssignableFrom(parameterType)) {
        return request.getPortletMode();
      }
      else if (WindowState.class.isAssignableFrom(parameterType)) {
        return request.getWindowState();
      }
      else if (PortalContext.class.isAssignableFrom(parameterType)) {
        return request.getPortalContext();
      }
      else if (Principal.class.isAssignableFrom(parameterType)) {
        return request.getUserPrincipal();
      }
      else if (Locale.class.equals(parameterType)) {
        return request.getLocale();
      }
      else if (InputStream.class.isAssignableFrom(parameterType)) {
        if (!(request instanceof ClientDataRequest)) {
          throw new IllegalStateException("InputStream can only get obtained for Action/ResourceRequest");
        }
        return ((ClientDataRequestrequest).getPortletInputStream();
      }
      else if (Reader.class.isAssignableFrom(parameterType)) {
        if (!(request instanceof ClientDataRequest)) {
          throw new IllegalStateException("Reader can only get obtained for Action/ResourceRequest");
        }
        return ((ClientDataRequestrequest).getReader();
      }
      else if (OutputStream.class.isAssignableFrom(parameterType)) {
        if (!(response instanceof MimeResponse)) {
          throw new IllegalStateException("OutputStream can only get obtained for Render/ResourceResponse");
        }
        return ((MimeResponseresponse).getPortletOutputStream();
      }
      else if (Writer.class.isAssignableFrom(parameterType)) {
        if (!(response instanceof MimeResponse)) {
          throw new IllegalStateException("Writer can only get obtained for Render/ResourceResponse");
        }
        return ((MimeResponseresponse).getWriter();
      }
      else if (Event.class.equals(parameterType)) {
        if (!(request instanceof EventRequest)) {
          throw new IllegalStateException("Event can only get obtained from EventRequest");
        }
        return ((EventRequestrequest).getEvent();
      }
      return super.resolveStandardArgument(parameterType, webRequest);
    }

    @SuppressWarnings("unchecked")
    public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel,
        PortletWebRequest webRequest) {
      // Invoke custom resolvers if present...
      if (customModelAndViewResolvers != null) {
        for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
          org.springframework.web.servlet.ModelAndView smav =
              mavResolver.resolveModelAndView(handlerMethod, handlerType, returnValue, implicitModel, webRequest);
          if (smav != ModelAndViewResolver.UNRESOLVED) {
            return (smav.isReference() ?
                new ModelAndView(smav.getViewName(), smav.getModelMap()) :
                new ModelAndView(smav.getView(), smav.getModelMap()));
          }
        }
      }

      if (returnValue instanceof ModelAndView) {
        ModelAndView mav = (ModelAndViewreturnValue;
        mav.getModelMap().mergeAttributes(implicitModel);
        return mav;
      }
      else if (returnValue instanceof org.springframework.web.servlet.ModelAndView) {
        org.springframework.web.servlet.ModelAndView smav = (org.springframework.web.servlet.ModelAndViewreturnValue;
        ModelAndView mav = (smav.isReference() ?
            new ModelAndView(smav.getViewName(), smav.getModelMap()) :
            new ModelAndView(smav.getView(), smav.getModelMap()));
        mav.getModelMap().mergeAttributes(implicitModel);
        return mav;
      }
      else if (returnValue instanceof Model) {
        return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((ModelreturnValue).asMap());
      }
      else if (returnValue instanceof View) {
        return new ModelAndView(returnValue).addAllObjects(implicitModel);
      }
      else if (handlerMethod.isAnnotationPresent(ModelAttribute.class)) {
        addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
        return new ModelAndView().addAllObjects(implicitModel);
      }
      else if (returnValue instanceof Map) {
        return new ModelAndView().addAllObjects(implicitModel).addAllObjects((MapreturnValue);
      }
      else if (returnValue instanceof String) {
        return new ModelAndView((StringreturnValue).addAllObjects(implicitModel);
      }
      else if (returnValue == null) {
        // Either returned null or was 'void' return.
        return null;
      }
      else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
        // Assume a single model attribute...
        addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
        return new ModelAndView().addAllObjects(implicitModel);
      }
      else {
        throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
      }
    }
  }


  /**
   * Holder for request mapping metadata. Allows for finding a best matching candidate.
   */
  private static class RequestMappingInfo {

    public final Set<PortletMode> modes = new HashSet<PortletMode>();

    public String phase;

    public String value;

    public final Set<String> methods = new HashSet<String>();

    public String[] params = new String[0];

    public String[] headers = new String[0];

    public void initStandardMapping(String[] modes, RequestMethod[] methods, String[] params, String[] headers) {
      for (String mode : modes) {
        this.modes.add(new PortletMode(mode));
      }
      for (RequestMethod method : methods) {
        this.methods.add(method.name());
      }
      this.params = StringUtils.mergeStringArrays(this.params, params);
      this.headers = StringUtils.mergeStringArrays(this.headers, headers);
    }

    public void initPhaseMapping(String phase, String value, String[] params) {
      if (this.phase != null) {
        throw new IllegalStateException(
            "Invalid mapping - more than one phase specified: '" this.phase + "', '" + phase + "'");
      }
      this.phase = phase;
      this.value = value;
      this.params = StringUtils.mergeStringArrays(this.params, params);
    }

    public boolean match(PortletRequest request) {
      if (!this.modes.isEmpty() && !this.modes.contains(request.getPortletMode())) {
        return false;
      }
      if (StringUtils.hasLength(this.phase&&
          !this.phase.equals(request.getAttribute(PortletRequest.LIFECYCLE_PHASE))) {
        return false;
      }
      if (StringUtils.hasLength(this.value)) {
        if (this.phase.equals(PortletRequest.ACTION_PHASE&&
            !this.value.equals(request.getParameter(ActionRequest.ACTION_NAME))) {
          return false;
        }
        else if (this.phase.equals(PortletRequest.RENDER_PHASE&&
            !(new WindowState(this.value)).equals(request.getWindowState())) {
          return false;
        }
        else if (this.phase.equals(PortletRequest.RESOURCE_PHASE&&
            !this.value.equals(((ResourceRequestrequest).getResourceID())) {
          return false;
        }
        else if (this.phase.equals(PortletRequest.EVENT_PHASE)) {
          Event event = ((EventRequestrequest).getEvent();
          if (!this.value.equals(event.getName()) && !this.value.equals(event.getQName().toString())) {
            return false;
          }
        }
      }
      return PortletAnnotationMappingUtils.checkRequestMethod(this.methods, request&&
          PortletAnnotationMappingUtils.checkParameters(this.params, request&&
          PortletAnnotationMappingUtils.checkHeaders(this.headers, request);
    }

    public boolean isBetterMatchThan(RequestMappingInfo other) {
      return ((!this.modes.isEmpty() && other.modes.isEmpty()) ||
          (StringUtils.hasLength(this.phase&& !StringUtils.hasLength(other.phase)) ||
          (StringUtils.hasLength(this.value&& !StringUtils.hasLength(other.value)) ||
          (!this.methods.isEmpty() && other.methods.isEmpty()) ||
          this.params.length > other.params.length);
    }

    @Override
    public boolean equals(Object obj) {
      RequestMappingInfo other = (RequestMappingInfoobj;
      return (this.modes.equals(other.modes&&
          ObjectUtils.nullSafeEquals(this.phase, other.phase&&
          ObjectUtils.nullSafeEquals(this.value, other.value&&
          this.methods.equals(other.methods&&
          Arrays.equals(this.params, other.params&&
          Arrays.equals(this.headers, other.headers));
    }

    @Override
    public int hashCode() {
      return (ObjectUtils.nullSafeHashCode(this.modes29 this.phase.hashCode());
    }
  }

}