001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.portlet.mvc.annotation;
018
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.io.Reader;
022import java.io.Writer;
023import java.lang.reflect.Method;
024import java.security.Principal;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.LinkedHashMap;
029import java.util.Locale;
030import java.util.Map;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import javax.portlet.ActionRequest;
034import javax.portlet.ActionResponse;
035import javax.portlet.ClientDataRequest;
036import javax.portlet.Event;
037import javax.portlet.EventRequest;
038import javax.portlet.EventResponse;
039import javax.portlet.MimeResponse;
040import javax.portlet.PortalContext;
041import javax.portlet.PortletException;
042import javax.portlet.PortletMode;
043import javax.portlet.PortletPreferences;
044import javax.portlet.PortletRequest;
045import javax.portlet.PortletResponse;
046import javax.portlet.PortletSession;
047import javax.portlet.RenderRequest;
048import javax.portlet.RenderResponse;
049import javax.portlet.ResourceRequest;
050import javax.portlet.ResourceResponse;
051import javax.portlet.StateAwareResponse;
052import javax.portlet.WindowState;
053import javax.servlet.http.Cookie;
054
055import org.springframework.beans.BeanUtils;
056import org.springframework.beans.factory.BeanFactory;
057import org.springframework.beans.factory.BeanFactoryAware;
058import org.springframework.beans.factory.config.BeanExpressionContext;
059import org.springframework.beans.factory.config.BeanExpressionResolver;
060import org.springframework.beans.factory.config.ConfigurableBeanFactory;
061import org.springframework.core.DefaultParameterNameDiscoverer;
062import org.springframework.core.Ordered;
063import org.springframework.core.ParameterNameDiscoverer;
064import org.springframework.core.annotation.AnnotationUtils;
065import org.springframework.ui.ExtendedModelMap;
066import org.springframework.ui.Model;
067import org.springframework.util.Assert;
068import org.springframework.util.ClassUtils;
069import org.springframework.util.ObjectUtils;
070import org.springframework.util.StringUtils;
071import org.springframework.validation.support.BindingAwareModelMap;
072import org.springframework.web.bind.WebDataBinder;
073import org.springframework.web.bind.annotation.InitBinder;
074import org.springframework.web.bind.annotation.ModelAttribute;
075import org.springframework.web.bind.annotation.RequestMapping;
076import org.springframework.web.bind.annotation.RequestMethod;
077import org.springframework.web.bind.annotation.RequestParam;
078import org.springframework.web.bind.annotation.SessionAttributes;
079import org.springframework.web.bind.support.DefaultSessionAttributeStore;
080import org.springframework.web.bind.support.SessionAttributeStore;
081import org.springframework.web.bind.support.WebArgumentResolver;
082import org.springframework.web.bind.support.WebBindingInitializer;
083import org.springframework.web.context.request.NativeWebRequest;
084import org.springframework.web.context.request.RequestScope;
085import org.springframework.web.multipart.MultipartRequest;
086import org.springframework.web.portlet.HandlerAdapter;
087import org.springframework.web.portlet.ModelAndView;
088import org.springframework.web.portlet.NoHandlerFoundException;
089import org.springframework.web.portlet.bind.MissingPortletRequestParameterException;
090import org.springframework.web.portlet.bind.PortletRequestDataBinder;
091import org.springframework.web.portlet.bind.annotation.ActionMapping;
092import org.springframework.web.portlet.bind.annotation.EventMapping;
093import org.springframework.web.portlet.bind.annotation.RenderMapping;
094import org.springframework.web.portlet.bind.annotation.ResourceMapping;
095import org.springframework.web.portlet.context.PortletWebRequest;
096import org.springframework.web.portlet.handler.PortletContentGenerator;
097import org.springframework.web.portlet.handler.PortletSessionRequiredException;
098import org.springframework.web.portlet.util.PortletUtils;
099import org.springframework.web.servlet.View;
100import org.springframework.web.servlet.mvc.annotation.ModelAndViewResolver;
101
102/**
103 * Implementation of the {@link org.springframework.web.portlet.HandlerAdapter}
104 * interface that maps handler methods based on portlet modes, action/render phases
105 * and request parameters expressed through the {@link RequestMapping} annotation.
106 *
107 * <p>Supports request parameter binding through the {@link RequestParam} annotation.
108 * Also supports the {@link ModelAttribute} annotation for exposing model attribute
109 * values to the view, as well as {@link InitBinder} for binder initialization methods
110 * and {@link SessionAttributes} for automatic session management of specific attributes.
111 *
112 * <p>This adapter can be customized through various bean properties.
113 * A common use case is to apply shared binder initialization logic through
114 * a custom {@link #setWebBindingInitializer WebBindingInitializer}.
115 *
116 * @author Juergen Hoeller
117 * @author Arjen Poutsma
118 * @since 2.5
119 * @see #setWebBindingInitializer
120 * @see #setSessionAttributeStore
121 */
122public class AnnotationMethodHandlerAdapter extends PortletContentGenerator
123                implements HandlerAdapter, Ordered, BeanFactoryAware {
124
125        public static final String IMPLICIT_MODEL_SESSION_ATTRIBUTE =
126                        AnnotationMethodHandlerAdapter.class.getName() + ".IMPLICIT_MODEL";
127
128        public static final String IMPLICIT_MODEL_RENDER_PARAMETER = "implicitModel";
129
130
131        private WebBindingInitializer webBindingInitializer;
132
133        private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
134
135        private int cacheSecondsForSessionAttributeHandlers = 0;
136
137        private boolean synchronizeOnSession = false;
138
139        private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
140
141        private WebArgumentResolver[] customArgumentResolvers;
142
143        private ModelAndViewResolver[] customModelAndViewResolvers;
144
145        private int order = Ordered.LOWEST_PRECEDENCE;
146
147        private ConfigurableBeanFactory beanFactory;
148
149        private BeanExpressionContext expressionContext;
150
151        private final Map<Class<?>, PortletHandlerMethodResolver> methodResolverCache =
152                        new ConcurrentHashMap<Class<?>, PortletHandlerMethodResolver>(64);
153
154
155        /**
156         * Specify a WebBindingInitializer which will apply pre-configured
157         * configuration to every DataBinder that this controller uses.
158         */
159        public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) {
160                this.webBindingInitializer = webBindingInitializer;
161        }
162
163        /**
164         * Specify the strategy to store session attributes with.
165         * <p>Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore},
166         * storing session attributes in the PortletSession, using the same
167         * attribute name as in the model.
168         */
169        public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore) {
170                Assert.notNull(sessionAttributeStore, "SessionAttributeStore must not be null");
171                this.sessionAttributeStore = sessionAttributeStore;
172        }
173
174        /**
175         * Cache content produced by {@code @SessionAttributes} annotated handlers
176         * for the given number of seconds. Default is 0, preventing caching completely.
177         * <p>In contrast to the "cacheSeconds" property which will apply to all general
178         * handlers (but not to {@code @SessionAttributes} annotated handlers), this
179         * setting will apply to {@code @SessionAttributes} annotated handlers only.
180         * @see #setCacheSeconds
181         * @see org.springframework.web.bind.annotation.SessionAttributes
182         */
183        public void setCacheSecondsForSessionAttributeHandlers(int cacheSecondsForSessionAttributeHandlers) {
184                this.cacheSecondsForSessionAttributeHandlers = cacheSecondsForSessionAttributeHandlers;
185        }
186
187        /**
188         * Set if controller execution should be synchronized on the session,
189         * to serialize parallel invocations from the same client.
190         * <p>More specifically, the execution of each handler method will get
191         * synchronized if this flag is "true". The best available session mutex
192         * will be used for the synchronization; ideally, this will be a mutex
193         * exposed by HttpSessionMutexListener.
194         * <p>The session mutex is guaranteed to be the same object during
195         * the entire lifetime of the session, available under the key defined
196         * by the {@code SESSION_MUTEX_ATTRIBUTE} constant. It serves as a
197         * safe reference to synchronize on for locking on the current session.
198         * <p>In many cases, the PortletSession reference itself is a safe mutex
199         * as well, since it will always be the same object reference for the
200         * same active logical session. However, this is not guaranteed across
201         * different servlet containers; the only 100% safe way is a session mutex.
202         * @see org.springframework.web.util.HttpSessionMutexListener
203         * @see org.springframework.web.portlet.util.PortletUtils#getSessionMutex(javax.portlet.PortletSession)
204         */
205        public void setSynchronizeOnSession(boolean synchronizeOnSession) {
206                this.synchronizeOnSession = synchronizeOnSession;
207        }
208
209        /**
210         * Set the ParameterNameDiscoverer to use for resolving method parameter
211         * names if needed (e.g. for default attribute names).
212         * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.
213         */
214        public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
215                this.parameterNameDiscoverer = parameterNameDiscoverer;
216        }
217
218        /**
219         * Set a custom WebArgumentResolver to use for special method parameter types.
220         * Such a custom WebArgumentResolver will kick in first, having a chance to
221         * resolve an argument value before the standard argument handling kicks in.
222         */
223        public void setCustomArgumentResolver(WebArgumentResolver argumentResolver) {
224                this.customArgumentResolvers = new WebArgumentResolver[] {argumentResolver};
225        }
226
227        /**
228         * Set one or more custom WebArgumentResolvers to use for special method
229         * parameter types. Any such custom WebArgumentResolver will kick in first,
230         * having a chance to resolve an argument value before the standard
231         * argument handling kicks in.
232         */
233        public void setCustomArgumentResolvers(WebArgumentResolver... argumentResolvers) {
234                this.customArgumentResolvers = argumentResolvers;
235        }
236
237        /**
238         * Set a custom ModelAndViewResolvers to use for special method return types.
239         * Such a custom ModelAndViewResolver will kick in first, having a chance to
240         * resolve an return value before the standard ModelAndView handling kicks in.
241         */
242        public void setCustomModelAndViewResolver(ModelAndViewResolver customModelAndViewResolver) {
243                this.customModelAndViewResolvers = new ModelAndViewResolver[]{customModelAndViewResolver};
244        }
245
246        /**
247         * Set one or more custom ModelAndViewResolvers to use for special method return types.
248         * Any such custom ModelAndViewResolver will kick in first, having a chance to
249         * resolve an return value before the standard ModelAndView handling kicks in.
250         */
251        public void setCustomModelAndViewResolvers(ModelAndViewResolver... customModelAndViewResolvers) {
252                this.customModelAndViewResolvers = customModelAndViewResolvers;
253        }
254
255        /**
256         * Specify the order value for this HandlerAdapter bean.
257         * <p>Default value is {@code Integer.MAX_VALUE}, meaning that it's non-ordered.
258         * @see org.springframework.core.Ordered#getOrder()
259         */
260        public void setOrder(int order) {
261                this.order = order;
262        }
263
264        @Override
265        public int getOrder() {
266                return this.order;
267        }
268
269        @Override
270        public void setBeanFactory(BeanFactory beanFactory) {
271                if (beanFactory instanceof ConfigurableBeanFactory) {
272                        this.beanFactory = (ConfigurableBeanFactory) beanFactory;
273                        this.expressionContext = new BeanExpressionContext(this.beanFactory, new RequestScope());
274                }
275        }
276
277
278        @Override
279        public boolean supports(Object handler) {
280                return getMethodResolver(handler).hasHandlerMethods();
281        }
282
283        @Override
284        public void handleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception {
285                Object returnValue = doHandle(request, response, handler);
286                if (returnValue != null) {
287                        throw new IllegalStateException("Invalid action method return value: " + returnValue);
288                }
289        }
290
291        @Override
292        public ModelAndView handleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception {
293                checkAndPrepare(request, response);
294                return doHandle(request, response, handler);
295        }
296
297        @Override
298        public ModelAndView handleResource(ResourceRequest request, ResourceResponse response, Object handler) throws Exception {
299                checkAndPrepare(request, response);
300                return doHandle(request, response, handler);
301        }
302
303        @Override
304        public void handleEvent(EventRequest request, EventResponse response, Object handler) throws Exception {
305                Object returnValue = doHandle(request, response, handler);
306                if (returnValue != null) {
307                        throw new IllegalStateException("Invalid event method return value: " + returnValue);
308                }
309        }
310
311
312        protected ModelAndView doHandle(PortletRequest request, PortletResponse response, Object handler) throws Exception {
313                ExtendedModelMap implicitModel = null;
314
315                if (response instanceof MimeResponse) {
316                        MimeResponse mimeResponse = (MimeResponse) response;
317                        // Detect implicit model from associated action phase.
318                        if (response instanceof RenderResponse) {
319                                PortletSession session = request.getPortletSession(false);
320                                if (session != null) {
321                                        if (request.getParameter(IMPLICIT_MODEL_RENDER_PARAMETER) != null) {
322                                                implicitModel = (ExtendedModelMap) session.getAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
323                                        }
324                                        else {
325                                                session.removeAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
326                                        }
327                                }
328                        }
329                        if (handler.getClass().getAnnotation(SessionAttributes.class) != null) {
330                                // Always prevent caching in case of session attribute management.
331                                checkAndPrepare(request, mimeResponse, this.cacheSecondsForSessionAttributeHandlers);
332                        }
333                        else {
334                                // Uses configured default cacheSeconds setting.
335                                checkAndPrepare(request, mimeResponse);
336                        }
337                }
338
339                if (implicitModel == null) {
340                        implicitModel = new BindingAwareModelMap();
341                }
342
343                // Execute invokeHandlerMethod in synchronized block if required.
344                if (this.synchronizeOnSession) {
345                        PortletSession session = request.getPortletSession(false);
346                        if (session != null) {
347                                Object mutex = PortletUtils.getSessionMutex(session);
348                                synchronized (mutex) {
349                                        return invokeHandlerMethod(request, response, handler, implicitModel);
350                                }
351                        }
352                }
353
354                return invokeHandlerMethod(request, response, handler, implicitModel);
355        }
356
357        @SuppressWarnings("unchecked")
358        private ModelAndView invokeHandlerMethod(
359                        PortletRequest request, PortletResponse response, Object handler, ExtendedModelMap implicitModel)
360                        throws Exception {
361
362                PortletWebRequest webRequest = new PortletWebRequest(request, response);
363                PortletHandlerMethodResolver methodResolver = getMethodResolver(handler);
364                Method handlerMethod = methodResolver.resolveHandlerMethod(request);
365                PortletHandlerMethodInvoker methodInvoker = new PortletHandlerMethodInvoker(methodResolver);
366
367                Object result = methodInvoker.invokeHandlerMethod(handlerMethod, handler, webRequest, implicitModel);
368                ModelAndView mav = methodInvoker.getModelAndView(handlerMethod, handler.getClass(), result, implicitModel,
369                                webRequest);
370                methodInvoker.updateModelAttributes(
371                                handler, (mav != null ? mav.getModel() : null), implicitModel, webRequest);
372
373                // Expose implicit model for subsequent render phase.
374                if (response instanceof StateAwareResponse && !implicitModel.isEmpty()) {
375                        StateAwareResponse stateResponse = (StateAwareResponse) response;
376                        Map<?, ?> modelToStore = implicitModel;
377                        try {
378                                stateResponse.setRenderParameter(IMPLICIT_MODEL_RENDER_PARAMETER, Boolean.TRUE.toString());
379                                if (response instanceof EventResponse) {
380                                        // Update the existing model, if any, when responding to an event -
381                                        // whereas we're replacing the model in case of an action response.
382                                        Map<String, Object> existingModel = (Map<String, Object>)
383                                                        request.getPortletSession().getAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE);
384                                        if (existingModel != null) {
385                                                existingModel.putAll(implicitModel);
386                                                modelToStore = existingModel;
387                                        }
388                                }
389                                request.getPortletSession().setAttribute(IMPLICIT_MODEL_SESSION_ATTRIBUTE, modelToStore);
390                        }
391                        catch (IllegalStateException ex) {
392                                // Probably sendRedirect called... no need to expose model to render phase.
393                        }
394                }
395
396                return mav;
397        }
398
399        /**
400         * Build a HandlerMethodResolver for the given handler type.
401         */
402        private PortletHandlerMethodResolver getMethodResolver(Object handler) {
403                Class<?> handlerClass = ClassUtils.getUserClass(handler);
404                PortletHandlerMethodResolver resolver = this.methodResolverCache.get(handlerClass);
405                if (resolver == null) {
406                        synchronized (this.methodResolverCache) {
407                                resolver = this.methodResolverCache.get(handlerClass);
408                                if (resolver == null) {
409                                        resolver = new PortletHandlerMethodResolver(handlerClass);
410                                        this.methodResolverCache.put(handlerClass, resolver);
411                                }
412                        }
413                }
414                return resolver;
415        }
416
417        /**
418         * Template method for creating a new PortletRequestDataBinder instance.
419         * <p>The default implementation creates a standard PortletRequestDataBinder.
420         * This can be overridden for custom PortletRequestDataBinder subclasses.
421         * @param request current portlet request
422         * @param target the target object to bind onto (or {@code null}
423         * if the binder is just used to convert a plain parameter value)
424         * @param objectName the objectName of the target object
425         * @return the PortletRequestDataBinder instance to use
426         * @throws Exception in case of invalid state or arguments
427         * @see PortletRequestDataBinder#bind(javax.portlet.PortletRequest)
428         */
429        protected PortletRequestDataBinder createBinder(PortletRequest request, Object target, String objectName) throws Exception {
430                return new PortletRequestDataBinder(target, objectName);
431        }
432
433
434        /**
435         * Portlet-specific subclass of {@code HandlerMethodResolver}.
436         */
437        @SuppressWarnings("deprecation")
438        private static class PortletHandlerMethodResolver extends org.springframework.web.bind.annotation.support.HandlerMethodResolver {
439
440                private final Map<Method, RequestMappingInfo> mappings = new HashMap<Method, RequestMappingInfo>();
441
442                public PortletHandlerMethodResolver(Class<?> handlerType) {
443                        init(handlerType);
444                }
445
446                @Override
447                protected boolean isHandlerMethod(Method method) {
448                        if (this.mappings.containsKey(method)) {
449                                return true;
450                        }
451                        RequestMappingInfo mappingInfo = new RequestMappingInfo();
452                        ActionMapping actionMapping = AnnotationUtils.findAnnotation(method, ActionMapping.class);
453                        RenderMapping renderMapping = AnnotationUtils.findAnnotation(method, RenderMapping.class);
454                        ResourceMapping resourceMapping = AnnotationUtils.findAnnotation(method, ResourceMapping.class);
455                        EventMapping eventMapping = AnnotationUtils.findAnnotation(method, EventMapping.class);
456                        RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
457                        if (actionMapping != null) {
458                                mappingInfo.initPhaseMapping(PortletRequest.ACTION_PHASE, actionMapping.name(), actionMapping.params());
459                        }
460                        if (renderMapping != null) {
461                                mappingInfo.initPhaseMapping(PortletRequest.RENDER_PHASE, renderMapping.windowState(), renderMapping.params());
462                        }
463                        if (resourceMapping != null) {
464                                mappingInfo.initPhaseMapping(PortletRequest.RESOURCE_PHASE, resourceMapping.value(), new String[0]);
465                        }
466                        if (eventMapping != null) {
467                                mappingInfo.initPhaseMapping(PortletRequest.EVENT_PHASE, eventMapping.value(), new String[0]);
468                        }
469                        if (requestMapping != null) {
470                                mappingInfo.initStandardMapping(requestMapping.value(), requestMapping.method(),
471                                                requestMapping.params(), requestMapping.headers());
472                                if (mappingInfo.phase == null) {
473                                        mappingInfo.phase = determineDefaultPhase(method);
474                                }
475                        }
476                        if (mappingInfo.phase != null) {
477                                this.mappings.put(method, mappingInfo);
478                                return true;
479                        }
480                        return false;
481                }
482
483                public Method resolveHandlerMethod(PortletRequest request) throws PortletException {
484                        Map<RequestMappingInfo, Method> targetHandlerMethods = new LinkedHashMap<RequestMappingInfo, Method>();
485                        for (Method handlerMethod : getHandlerMethods()) {
486                                RequestMappingInfo mappingInfo = this.mappings.get(handlerMethod);
487                                if (mappingInfo.match(request)) {
488                                        Method oldMappedMethod = targetHandlerMethods.put(mappingInfo, handlerMethod);
489                                        if (oldMappedMethod != null && oldMappedMethod != handlerMethod) {
490                                                throw new IllegalStateException("Ambiguous handler methods mapped for portlet mode '" +
491                                                                request.getPortletMode() + "': {" + oldMappedMethod + ", " + handlerMethod +
492                                                                "}. If you intend to handle the same mode in multiple methods, then factor " +
493                                                                "them out into a dedicated handler class with that mode mapped at the type level!");
494                                        }
495                                }
496                        }
497                        if (!targetHandlerMethods.isEmpty()) {
498                                if (targetHandlerMethods.size() == 1) {
499                                        return targetHandlerMethods.values().iterator().next();
500                                }
501                                else {
502                                        RequestMappingInfo bestMappingMatch = null;
503                                        for (RequestMappingInfo mapping : targetHandlerMethods.keySet()) {
504                                                if (bestMappingMatch == null) {
505                                                        bestMappingMatch = mapping;
506                                                }
507                                                else {
508                                                        if (mapping.isBetterMatchThan(bestMappingMatch)) {
509                                                                bestMappingMatch = mapping;
510                                                        }
511                                                }
512                                        }
513                                        return targetHandlerMethods.get(bestMappingMatch);
514                                }
515                        }
516                        else {
517                                throw new NoHandlerFoundException("No matching handler method found for portlet request", request);
518                        }
519                }
520
521                private String determineDefaultPhase(Method handlerMethod) {
522                        if (void.class != handlerMethod.getReturnType()) {
523                                return PortletRequest.RENDER_PHASE;
524                        }
525                        for (Class<?> argType : handlerMethod.getParameterTypes()) {
526                                if (ActionRequest.class.isAssignableFrom(argType) || ActionResponse.class.isAssignableFrom(argType) ||
527                                                InputStream.class.isAssignableFrom(argType) || Reader.class.isAssignableFrom(argType)) {
528                                        return PortletRequest.ACTION_PHASE;
529                                }
530                                else if (RenderRequest.class.isAssignableFrom(argType) || RenderResponse.class.isAssignableFrom(argType) ||
531                                                OutputStream.class.isAssignableFrom(argType) || Writer.class.isAssignableFrom(argType)) {
532                                        return PortletRequest.RENDER_PHASE;
533                                }
534                                else if (ResourceRequest.class.isAssignableFrom(argType) || ResourceResponse.class.isAssignableFrom(argType)) {
535                                        return PortletRequest.RESOURCE_PHASE;
536                                }
537                                else if (EventRequest.class.isAssignableFrom(argType) || EventResponse.class.isAssignableFrom(argType)) {
538                                        return PortletRequest.EVENT_PHASE;
539                                }
540                        }
541                        return "";
542                }
543        }
544
545
546        /**
547         * Portlet-specific subclass of {@code HandlerMethodInvoker}.
548         */
549        @SuppressWarnings("deprecation")
550        private class PortletHandlerMethodInvoker extends org.springframework.web.bind.annotation.support.HandlerMethodInvoker {
551
552                public PortletHandlerMethodInvoker(org.springframework.web.bind.annotation.support.HandlerMethodResolver resolver) {
553                        super(resolver, webBindingInitializer, sessionAttributeStore,
554                                        parameterNameDiscoverer, customArgumentResolvers, null);
555                }
556
557                @Override
558                protected void raiseMissingParameterException(String paramName, Class<?> paramType) throws Exception {
559                        throw new MissingPortletRequestParameterException(paramName, paramType.getSimpleName());
560                }
561
562                @Override
563                protected void raiseSessionRequiredException(String message) throws Exception {
564                        throw new PortletSessionRequiredException(message);
565                }
566
567                @Override
568                protected WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
569                                throws Exception {
570
571                        return AnnotationMethodHandlerAdapter.this.createBinder(
572                                        webRequest.getNativeRequest(PortletRequest.class), target, objectName);
573                }
574
575                @Override
576                protected void doBind(WebDataBinder binder, NativeWebRequest webRequest) throws Exception {
577                        PortletRequestDataBinder portletBinder = (PortletRequestDataBinder) binder;
578                        portletBinder.bind(webRequest.getNativeRequest(PortletRequest.class));
579                }
580
581                @Override
582                protected Object resolveDefaultValue(String value) {
583                        if (beanFactory == null) {
584                                return value;
585                        }
586                        String placeholdersResolved = beanFactory.resolveEmbeddedValue(value);
587                        BeanExpressionResolver exprResolver = beanFactory.getBeanExpressionResolver();
588                        if (exprResolver == null) {
589                                return value;
590                        }
591                        return exprResolver.evaluate(placeholdersResolved, expressionContext);
592                }
593
594                @Override
595                protected Object resolveCookieValue(String cookieName, Class<?> paramType, NativeWebRequest webRequest)
596                                throws Exception {
597
598                        PortletRequest portletRequest = webRequest.getNativeRequest(PortletRequest.class);
599                        Cookie cookieValue = PortletUtils.getCookie(portletRequest, cookieName);
600                        if (Cookie.class.isAssignableFrom(paramType)) {
601                                return cookieValue;
602                        }
603                        else if (cookieValue != null) {
604                                return cookieValue.getValue();
605                        }
606                        else {
607                                return null;
608                        }
609                }
610
611                @Override
612                protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest)
613                                throws Exception {
614
615                        PortletRequest request = webRequest.getNativeRequest(PortletRequest.class);
616                        PortletResponse response = webRequest.getNativeResponse(PortletResponse.class);
617
618                        if (PortletRequest.class.isAssignableFrom(parameterType) ||
619                                        MultipartRequest.class.isAssignableFrom(parameterType)) {
620                                Object nativeRequest = webRequest.getNativeRequest(parameterType);
621                                if (nativeRequest == null) {
622                                        throw new IllegalStateException(
623                                                        "Current request is not of type [" + parameterType.getName() + "]: " + request);
624                                }
625                                return nativeRequest;
626                        }
627                        else if (PortletResponse.class.isAssignableFrom(parameterType)) {
628                                Object nativeResponse = webRequest.getNativeResponse(parameterType);
629                                if (nativeResponse == null) {
630                                        throw new IllegalStateException(
631                                                        "Current response is not of type [" + parameterType.getName() + "]: " + response);
632                                }
633                                return nativeResponse;
634                        }
635                        else if (PortletSession.class.isAssignableFrom(parameterType)) {
636                                return request.getPortletSession();
637                        }
638                        else if (PortletPreferences.class.isAssignableFrom(parameterType)) {
639                                return request.getPreferences();
640                        }
641                        else if (PortletMode.class.isAssignableFrom(parameterType)) {
642                                return request.getPortletMode();
643                        }
644                        else if (WindowState.class.isAssignableFrom(parameterType)) {
645                                return request.getWindowState();
646                        }
647                        else if (PortalContext.class.isAssignableFrom(parameterType)) {
648                                return request.getPortalContext();
649                        }
650                        else if (Principal.class.isAssignableFrom(parameterType)) {
651                                return request.getUserPrincipal();
652                        }
653                        else if (Locale.class == parameterType) {
654                                return request.getLocale();
655                        }
656                        else if (InputStream.class.isAssignableFrom(parameterType)) {
657                                if (!(request instanceof ClientDataRequest)) {
658                                        throw new IllegalStateException("InputStream can only get obtained for Action/ResourceRequest");
659                                }
660                                return ((ClientDataRequest) request).getPortletInputStream();
661                        }
662                        else if (Reader.class.isAssignableFrom(parameterType)) {
663                                if (!(request instanceof ClientDataRequest)) {
664                                        throw new IllegalStateException("Reader can only get obtained for Action/ResourceRequest");
665                                }
666                                return ((ClientDataRequest) request).getReader();
667                        }
668                        else if (OutputStream.class.isAssignableFrom(parameterType)) {
669                                if (!(response instanceof MimeResponse)) {
670                                        throw new IllegalStateException("OutputStream can only get obtained for Render/ResourceResponse");
671                                }
672                                return ((MimeResponse) response).getPortletOutputStream();
673                        }
674                        else if (Writer.class.isAssignableFrom(parameterType)) {
675                                if (!(response instanceof MimeResponse)) {
676                                        throw new IllegalStateException("Writer can only get obtained for Render/ResourceResponse");
677                                }
678                                return ((MimeResponse) response).getWriter();
679                        }
680                        else if (Event.class == parameterType) {
681                                if (!(request instanceof EventRequest)) {
682                                        throw new IllegalStateException("Event can only get obtained from EventRequest");
683                                }
684                                return ((EventRequest) request).getEvent();
685                        }
686                        return super.resolveStandardArgument(parameterType, webRequest);
687                }
688
689                @SuppressWarnings("unchecked")
690                public ModelAndView getModelAndView(Method handlerMethod, Class<?> handlerType, Object returnValue, ExtendedModelMap implicitModel,
691                                PortletWebRequest webRequest) {
692                        // Invoke custom resolvers if present...
693                        if (customModelAndViewResolvers != null) {
694                                for (ModelAndViewResolver mavResolver : customModelAndViewResolvers) {
695                                        org.springframework.web.servlet.ModelAndView smav =
696                                                        mavResolver.resolveModelAndView(handlerMethod, handlerType, returnValue, implicitModel, webRequest);
697                                        if (smav != ModelAndViewResolver.UNRESOLVED) {
698                                                return (smav.isReference() ?
699                                                                new ModelAndView(smav.getViewName(), smav.getModelMap()) :
700                                                                new ModelAndView(smav.getView(), smav.getModelMap()));
701                                        }
702                                }
703                        }
704
705                        if (returnValue instanceof ModelAndView) {
706                                ModelAndView mav = (ModelAndView) returnValue;
707                                mav.getModelMap().mergeAttributes(implicitModel);
708                                return mav;
709                        }
710                        else if (returnValue instanceof org.springframework.web.servlet.ModelAndView) {
711                                org.springframework.web.servlet.ModelAndView smav = (org.springframework.web.servlet.ModelAndView) returnValue;
712                                ModelAndView mav = (smav.isReference() ?
713                                                new ModelAndView(smav.getViewName(), smav.getModelMap()) :
714                                                new ModelAndView(smav.getView(), smav.getModelMap()));
715                                mav.getModelMap().mergeAttributes(implicitModel);
716                                return mav;
717                        }
718                        else if (returnValue instanceof Model) {
719                                return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());
720                        }
721                        else if (returnValue instanceof View) {
722                                return new ModelAndView(returnValue).addAllObjects(implicitModel);
723                        }
724                        else if (handlerMethod.isAnnotationPresent(ModelAttribute.class)) {
725                                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
726                                return new ModelAndView().addAllObjects(implicitModel);
727                        }
728                        else if (returnValue instanceof Map) {
729                                return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map<String, Object>) returnValue);
730                        }
731                        else if (returnValue instanceof String) {
732                                return new ModelAndView((String) returnValue).addAllObjects(implicitModel);
733                        }
734                        else if (returnValue == null) {
735                                // Either returned null or was 'void' return.
736                                return null;
737                        }
738                        else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {
739                                // Assume a single model attribute...
740                                addReturnValueAsModelAttribute(handlerMethod, handlerType, returnValue, implicitModel);
741                                return new ModelAndView().addAllObjects(implicitModel);
742                        }
743                        else {
744                                throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);
745                        }
746                }
747        }
748
749
750        /**
751         * Holder for request mapping metadata. Allows for finding a best matching candidate.
752         */
753        private static class RequestMappingInfo {
754
755                public final Set<PortletMode> modes = new HashSet<PortletMode>();
756
757                public String phase;
758
759                public String value;
760
761                public final Set<String> methods = new HashSet<String>();
762
763                public String[] params = new String[0];
764
765                public String[] headers = new String[0];
766
767                public void initStandardMapping(String[] modes, RequestMethod[] methods, String[] params, String[] headers) {
768                        for (String mode : modes) {
769                                this.modes.add(new PortletMode(mode));
770                        }
771                        for (RequestMethod method : methods) {
772                                this.methods.add(method.name());
773                        }
774                        this.params = PortletAnnotationMappingUtils.mergeStringArrays(this.params, params);
775                        this.headers = PortletAnnotationMappingUtils.mergeStringArrays(this.headers, headers);
776                }
777
778                public void initPhaseMapping(String phase, String value, String[] params) {
779                        if (this.phase != null) {
780                                throw new IllegalStateException(
781                                                "Invalid mapping - more than one phase specified: '" + this.phase + "', '" + phase + "'");
782                        }
783                        this.phase = phase;
784                        this.value = value;
785                        this.params = PortletAnnotationMappingUtils.mergeStringArrays(this.params, params);
786                }
787
788                public boolean match(PortletRequest request) {
789                        if (!this.modes.isEmpty() && !this.modes.contains(request.getPortletMode())) {
790                                return false;
791                        }
792                        if (StringUtils.hasLength(this.phase) &&
793                                        !this.phase.equals(request.getAttribute(PortletRequest.LIFECYCLE_PHASE))) {
794                                return false;
795                        }
796                        if (StringUtils.hasLength(this.value)) {
797                                if (this.phase.equals(PortletRequest.ACTION_PHASE) &&
798                                                !this.value.equals(request.getParameter(ActionRequest.ACTION_NAME))) {
799                                        return false;
800                                }
801                                else if (this.phase.equals(PortletRequest.RENDER_PHASE) &&
802                                                !(new WindowState(this.value)).equals(request.getWindowState())) {
803                                        return false;
804                                }
805                                else if (this.phase.equals(PortletRequest.RESOURCE_PHASE) &&
806                                                !this.value.equals(((ResourceRequest) request).getResourceID())) {
807                                        return false;
808                                }
809                                else if (this.phase.equals(PortletRequest.EVENT_PHASE)) {
810                                        Event event = ((EventRequest) request).getEvent();
811                                        if (!this.value.equals(event.getName()) && !this.value.equals(event.getQName().toString())) {
812                                                return false;
813                                        }
814                                }
815                        }
816                        return (PortletAnnotationMappingUtils.checkRequestMethod(this.methods, request) &&
817                                        PortletAnnotationMappingUtils.checkParameters(this.params, request) &&
818                                        PortletAnnotationMappingUtils.checkHeaders(this.headers, request));
819                }
820
821                public boolean isBetterMatchThan(RequestMappingInfo other) {
822                        return ((!this.modes.isEmpty() && other.modes.isEmpty()) ||
823                                        (StringUtils.hasLength(this.phase) && !StringUtils.hasLength(other.phase)) ||
824                                        (StringUtils.hasLength(this.value) && !StringUtils.hasLength(other.value)) ||
825                                        (!this.methods.isEmpty() && other.methods.isEmpty()) ||
826                                        this.params.length > other.params.length);
827                }
828
829                @Override
830                public boolean equals(Object obj) {
831                        RequestMappingInfo other = (RequestMappingInfo) obj;
832                        return (this.modes.equals(other.modes) &&
833                                        ObjectUtils.nullSafeEquals(this.phase, other.phase) &&
834                                        ObjectUtils.nullSafeEquals(this.value, other.value) &&
835                                        this.methods.equals(other.methods) &&
836                                        Arrays.equals(this.params, other.params) &&
837                                        Arrays.equals(this.headers, other.headers));
838                }
839
840                @Override
841                public int hashCode() {
842                        return (ObjectUtils.nullSafeHashCode(this.modes) * 29 + this.phase.hashCode());
843                }
844        }
845
846}