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}