View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.web.portlet;
18  
19  import java.io.IOException;
20  import java.security.Principal;
21  import java.util.Map;
22  import javax.portlet.ActionRequest;
23  import javax.portlet.ActionResponse;
24  import javax.portlet.EventRequest;
25  import javax.portlet.EventResponse;
26  import javax.portlet.PortletException;
27  import javax.portlet.PortletRequest;
28  import javax.portlet.PortletResponse;
29  import javax.portlet.RenderRequest;
30  import javax.portlet.RenderResponse;
31  import javax.portlet.ResourceRequest;
32  import javax.portlet.ResourceResponse;
33  
34  import org.springframework.beans.BeanUtils;
35  import org.springframework.context.ApplicationContext;
36  import org.springframework.context.ApplicationContextException;
37  import org.springframework.context.ApplicationListener;
38  import org.springframework.context.ConfigurableApplicationContext;
39  import org.springframework.context.event.ContextRefreshedEvent;
40  import org.springframework.context.event.SourceFilteringListener;
41  import org.springframework.context.i18n.LocaleContext;
42  import org.springframework.context.i18n.LocaleContextHolder;
43  import org.springframework.context.i18n.SimpleLocaleContext;
44  import org.springframework.core.env.ConfigurableEnvironment;
45  import org.springframework.web.context.request.RequestAttributes;
46  import org.springframework.web.context.request.RequestContextHolder;
47  import org.springframework.web.context.request.ServletRequestAttributes;
48  import org.springframework.web.portlet.context.ConfigurablePortletApplicationContext;
49  import org.springframework.web.portlet.context.PortletApplicationContextUtils;
50  import org.springframework.web.portlet.context.PortletRequestAttributes;
51  import org.springframework.web.portlet.context.PortletRequestHandledEvent;
52  import org.springframework.web.portlet.context.StandardPortletEnvironment;
53  import org.springframework.web.portlet.context.XmlPortletApplicationContext;
54  
55  /**
56   * Base portlet for Spring's portlet framework. Provides integration with
57   * a Spring application context, in a JavaBean-based overall solution.
58   *
59   * <p>This class offers the following functionality:
60   * <ul>
61   * <li>Manages a Portlet {@link org.springframework.context.ApplicationContext}
62   * instance per portlet. The portlet's configuration is determined by beans
63   * in the portlet's namespace.
64   * <li>Publishes events on request processing, whether or not a request is
65   * successfully handled.
66   * </ul>
67   *
68   * <p>Subclasses must implement {@link #doActionService} and {@link #doRenderService}
69   * to handle action and render requests. Because this extends {@link GenericPortletBean}
70   * rather than Portlet directly, bean properties are mapped onto it. Subclasses can
71   * override {@link #initFrameworkPortlet()} for custom initialization.
72   *
73   * <p>Regards a "contextClass" parameter at the portlet init-param level,
74   * falling back to the default context class
75   * ({@link org.springframework.web.portlet.context.XmlPortletApplicationContext})
76   * if not found. Note that, with the default FrameworkPortlet,
77   * a context class needs to implement the
78   * {@link org.springframework.web.portlet.context.ConfigurablePortletApplicationContext} SPI.
79   *
80   * <p>Passes a "contextConfigLocation" portlet init-param to the context instance,
81   * parsing it into potentially multiple file paths which can be separated by any
82   * number of commas and spaces, like "test-portlet.xml, myPortlet.xml".
83   * If not explicitly specified, the context implementation is supposed to build a
84   * default location from the namespace of the portlet.
85   *
86   * <p>Note: In case of multiple config locations, later bean definitions will
87   * override ones defined in earlier loaded files, at least when using one of
88   * Spring's default ApplicationContext implementations. This can be leveraged
89   * to deliberately override certain bean definitions via an extra XML file.
90   *
91   * <p>The default namespace is "'portlet-name'-portlet", e.g. "test-portlet" for a
92   * portlet-name "test" (leading to a "/WEB-INF/test-portlet.xml" default location
93   * with XmlPortletApplicationContext). The namespace can also be set explicitly via
94   * the "namespace" portlet init-param.
95   *
96   * @author William G. Thompson, Jr.
97   * @author John A. Lewis
98   * @author Juergen Hoeller
99   * @since 2.0
100  * @see #doActionService
101  * @see #doRenderService
102  * @see #setContextClass
103  * @see #setContextConfigLocation
104  * @see #setNamespace
105  */
106 public abstract class FrameworkPortlet extends GenericPortletBean
107 		implements ApplicationListener<ContextRefreshedEvent> {
108 
109 	/**
110 	 * Default context class for FrameworkPortlet.
111 	 * @see org.springframework.web.portlet.context.XmlPortletApplicationContext
112 	 */
113 	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlPortletApplicationContext.class;
114 
115 	/**
116 	 * Suffix for Portlet ApplicationContext namespaces. If a portlet of this class is
117 	 * given the name "test" in a context, the namespace used by the portlet will
118 	 * resolve to "test-portlet".
119 	 */
120 	public static final String DEFAULT_NAMESPACE_SUFFIX = "-portlet";
121 
122 	/**
123 	 * Prefix for the PortletContext attribute for the Portlet ApplicationContext.
124 	 * The completion is the portlet name.
125 	 */
126 	public static final String PORTLET_CONTEXT_PREFIX = FrameworkPortlet.class.getName() + ".CONTEXT.";
127 
128 	/**
129 	 * Default USER_INFO attribute names to search for the current username:
130 	 * "user.login.id", "user.name".
131 	 */
132 	public static final String[] DEFAULT_USERINFO_ATTRIBUTE_NAMES = {"user.login.id", "user.name"};
133 
134 
135 	/** Portlet ApplicationContext implementation class to use */
136 	private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
137 
138 	/** Namespace for this portlet */
139 	private String namespace;
140 
141 	/** Explicit context config location */
142 	private String contextConfigLocation;
143 
144 	/** Should we publish the context as a PortletContext attribute? */
145 	private boolean publishContext = true;
146 
147 	/** Should we publish a PortletRequestHandledEvent at the end of each request? */
148 	private boolean publishEvents = true;
149 
150 	/** Expose LocaleContext and RequestAttributes as inheritable for child threads? */
151 	private boolean threadContextInheritable = false;
152 
153 	/** USER_INFO attributes that may contain the username of the current user */
154 	private String[] userinfoUsernameAttributes = DEFAULT_USERINFO_ATTRIBUTE_NAMES;
155 
156 	/** ApplicationContext for this portlet */
157 	private ApplicationContext portletApplicationContext;
158 
159 	/** Flag used to detect whether onRefresh has already been called */
160 	private boolean refreshEventReceived = false;
161 
162 
163 	/**
164 	 * Set a custom context class. This class must be of type ApplicationContext;
165 	 * when using the default FrameworkPortlet implementation, the context class
166 	 * must also implement ConfigurablePortletApplicationContext.
167 	 * @see #createPortletApplicationContext
168 	 */
169 	public void setContextClass(Class<?> contextClass) {
170 		this.contextClass = contextClass;
171 	}
172 
173 	/**
174 	 * Return the custom context class.
175 	 */
176 	public Class<?> getContextClass() {
177 		return this.contextClass;
178 	}
179 
180 	/**
181 	 * Set a custom namespace for this portlet,
182 	 * to be used for building a default context config location.
183 	 */
184 	public void setNamespace(String namespace) {
185 		this.namespace = namespace;
186 	}
187 
188 	/**
189 	 * Return the namespace for this portlet, falling back to default scheme if
190 	 * no custom namespace was set. (e.g. "test-portlet" for a portlet named "test")
191 	 */
192 	public String getNamespace() {
193 		return (this.namespace != null) ? this.namespace : getPortletName() + DEFAULT_NAMESPACE_SUFFIX;
194 	}
195 
196 	/**
197 	 * Set the context config location explicitly, instead of relying on the default
198 	 * location built from the namespace. This location string can consist of
199 	 * multiple locations separated by any number of commas and spaces.
200 	 */
201 	public void setContextConfigLocation(String contextConfigLocation) {
202 		this.contextConfigLocation = contextConfigLocation;
203 	}
204 
205 	/**
206 	 * Return the explicit context config location, if any.
207 	 */
208 	public String getContextConfigLocation() {
209 		return this.contextConfigLocation;
210 	}
211 
212 	/**
213 	 * Set whether to publish this portlet's context as a PortletContext attribute,
214 	 * available to all objects in the web container. Default is true.
215 	 * <p>This is especially handy during testing, although it is debatable whether
216 	 * it's good practice to let other application objects access the context this way.
217 	 */
218 	public void setPublishContext(boolean publishContext) {
219 		this.publishContext = publishContext;
220 	}
221 
222 	/**
223 	 * Set whether this portlet should publish a PortletRequestHandledEvent at the end
224 	 * of each request. Default is true; can be turned off for a slight performance
225 	 * improvement, provided that no ApplicationListeners rely on such events.
226 	 * @see org.springframework.web.portlet.context.PortletRequestHandledEvent
227 	 */
228 	public void setPublishEvents(boolean publishEvents) {
229 		this.publishEvents = publishEvents;
230 	}
231 
232 	/**
233 	 * Set whether to expose the LocaleContext and RequestAttributes as inheritable
234 	 * for child threads (using an {@link java.lang.InheritableThreadLocal}).
235 	 * <p>Default is "false", to avoid side effects on spawned background threads.
236 	 * Switch this to "true" to enable inheritance for custom child threads which
237 	 * are spawned during request processing and only used for this request
238 	 * (that is, ending after their initial task, without reuse of the thread).
239 	 * <p><b>WARNING:</b> Do not use inheritance for child threads if you are
240 	 * accessing a thread pool which is configured to potentially add new threads
241 	 * on demand (e.g. a JDK {@link java.util.concurrent.ThreadPoolExecutor}),
242 	 * since this will expose the inherited context to such a pooled thread.
243 	 */
244 	public void setThreadContextInheritable(boolean threadContextInheritable) {
245 		this.threadContextInheritable = threadContextInheritable;
246 	}
247 
248 	/**
249 	 * Set the list of attributes to search in the USER_INFO map when trying
250 	 * to find the username of the current user.
251 	 * @see #getUsernameForRequest
252 	 */
253 	public void setUserinfoUsernameAttributes(String[] userinfoUsernameAttributes) {
254 		this.userinfoUsernameAttributes = userinfoUsernameAttributes;
255 	}
256 
257 
258 	/**
259 	 * Overridden method of GenericPortletBean, invoked after any bean properties
260 	 * have been set. Creates this portlet's ApplicationContext.
261 	 */
262 	@Override
263 	protected final void initPortletBean() throws PortletException {
264 		getPortletContext().log("Initializing Spring FrameworkPortlet '" + getPortletName() + "'");
265 		if (logger.isInfoEnabled()) {
266 			logger.info("FrameworkPortlet '" + getPortletName() + "': initialization started");
267 		}
268 		long startTime = System.currentTimeMillis();
269 
270 		try {
271 			this.portletApplicationContext = initPortletApplicationContext();
272 			initFrameworkPortlet();
273 		}
274 		catch (PortletException ex) {
275 			logger.error("Context initialization failed", ex);
276 			throw ex;
277 		}
278 		catch (RuntimeException ex) {
279 			logger.error("Context initialization failed", ex);
280 			throw ex;
281 		}
282 
283 		if (logger.isInfoEnabled()) {
284 			long elapsedTime = System.currentTimeMillis() - startTime;
285 			logger.info("FrameworkPortlet '" + getPortletName() + "': initialization completed in " + elapsedTime + " ms");
286 		}
287 	}
288 
289 	/**
290 	 * Initialize and publish the Portlet ApplicationContext for this portlet.
291 	 * <p>Delegates to {@link #createPortletApplicationContext} for actual creation.
292 	 * Can be overridden in subclasses.
293 	 * @return the ApplicationContext for this portlet
294 	 */
295 	protected ApplicationContext initPortletApplicationContext() {
296 		ApplicationContext parent = PortletApplicationContextUtils.getWebApplicationContext(getPortletContext());
297 		ApplicationContext pac = createPortletApplicationContext(parent);
298 
299 		if (!this.refreshEventReceived) {
300 			// Apparently not a ConfigurableApplicationContext with refresh support:
301 			// triggering initial onRefresh manually here.
302 			onRefresh(pac);
303 		}
304 
305 		if (this.publishContext) {
306 			// publish the context as a portlet context attribute
307 			String attName = getPortletContextAttributeName();
308 			getPortletContext().setAttribute(attName, pac);
309 			if (logger.isDebugEnabled()) {
310 				logger.debug("Published ApplicationContext of portlet '" + getPortletName() +
311 						"' as PortletContext attribute with name [" + attName + "]");
312 			}
313 		}
314 		return pac;
315 	}
316 
317 	/**
318 	 * Instantiate the Portlet ApplicationContext for this portlet, either a default
319 	 * XmlPortletApplicationContext or a custom context class if set.
320 	 * <p>This implementation expects custom contexts to implement
321 	 * ConfigurablePortletApplicationContext. Can be overridden in subclasses.
322 	 * @param parent the parent ApplicationContext to use, or null if none
323 	 * @return the Portlet ApplicationContext for this portlet
324 	 * @see #setContextClass
325 	 * @see org.springframework.web.portlet.context.XmlPortletApplicationContext
326 	 */
327 	protected ApplicationContext createPortletApplicationContext(ApplicationContext parent) {
328 		Class<?> contextClass = getContextClass();
329 		if (logger.isDebugEnabled()) {
330 			logger.debug("Portlet with name '" + getPortletName() +
331 					"' will try to create custom ApplicationContext context of class '" +
332 					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
333 		}
334 		if (!ConfigurablePortletApplicationContext.class.isAssignableFrom(contextClass)) {
335 			throw new ApplicationContextException("Fatal initialization error in portlet with name '" + getPortletName() +
336 					"': custom ApplicationContext class [" + contextClass.getName() +
337 					"] is not of type ConfigurablePortletApplicationContext");
338 		}
339 		ConfigurablePortletApplicationContext pac =
340 				(ConfigurablePortletApplicationContext) BeanUtils.instantiateClass(contextClass);
341 
342 		// Assign the best possible id value.
343 		String portletContextName = getPortletContext().getPortletContextName();
344 		if (portletContextName != null) {
345 			pac.setId(ConfigurablePortletApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + portletContextName + "." + getPortletName());
346 		}
347 		else {
348 			pac.setId(ConfigurablePortletApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + getPortletName());
349 		}
350 
351 		pac.setEnvironment(getEnvironment());
352 		pac.setParent(parent);
353 		pac.setPortletContext(getPortletContext());
354 		pac.setPortletConfig(getPortletConfig());
355 		pac.setNamespace(getNamespace());
356 		pac.setConfigLocation(getContextConfigLocation());
357 		pac.addApplicationListener(new SourceFilteringListener(pac, this));
358 
359 		// The wac environment's #initPropertySources will be called in any case when the context
360 		// is refreshed; do it eagerly here to ensure portlet property sources are in place for
361 		// use in any post-processing or initialization that occurs below prior to #refresh
362 		ConfigurableEnvironment env = pac.getEnvironment();
363 		if (env instanceof StandardPortletEnvironment) {
364 			((StandardPortletEnvironment) env).initPropertySources(pac.getServletContext(), getPortletContext(), getPortletConfig());
365 		}
366 
367 		postProcessPortletApplicationContext(pac);
368 		pac.refresh();
369 
370 		return pac;
371 	}
372 
373 	/**
374 	 * Post-process the given Portlet ApplicationContext before it is refreshed
375 	 * and activated as context for this portlet.
376 	 * <p>The default implementation is empty. {@code refresh()} will
377 	 * be called automatically after this method returns.
378 	 * @param pac the configured Portlet ApplicationContext (not refreshed yet)
379 	 * @see #createPortletApplicationContext
380 	 * @see ConfigurableApplicationContext#refresh()
381 	 */
382 	protected void postProcessPortletApplicationContext(ConfigurableApplicationContext pac) {
383 	}
384 
385 	/**
386 	 * Return the PortletContext attribute name for this portlets's ApplicationContext.
387 	 * <p>The default implementation returns PORTLET_CONTEXT_PREFIX + portlet name.
388 	 * @see #PORTLET_CONTEXT_PREFIX
389 	 * @see #getPortletName
390 	 */
391 	public String getPortletContextAttributeName() {
392 		return PORTLET_CONTEXT_PREFIX + getPortletName();
393 	}
394 
395 	/**
396 	 * Return this portlet's ApplicationContext.
397 	 */
398 	public final ApplicationContext getPortletApplicationContext() {
399 		return this.portletApplicationContext;
400 	}
401 
402 
403 	/**
404 	 * This method will be invoked after any bean properties have been set and
405 	 * the ApplicationContext has been loaded.
406 	 * <p>The default implementation is empty; subclasses may override this method
407 	 * to perform any initialization they require.
408 	 * @throws PortletException in case of an initialization exception
409 	 */
410 	protected void initFrameworkPortlet() throws PortletException {
411 	}
412 
413 	/**
414 	 * Refresh this portlet's application context, as well as the
415 	 * dependent state of the portlet.
416 	 * @see #getPortletApplicationContext()
417 	 * @see org.springframework.context.ConfigurableApplicationContext#refresh()
418 	 */
419 	public void refresh() {
420 		ApplicationContext pac = getPortletApplicationContext();
421 		if (!(pac instanceof ConfigurableApplicationContext)) {
422 			throw new IllegalStateException("Portlet ApplicationContext does not support refresh: " + pac);
423 		}
424 		((ConfigurableApplicationContext) pac).refresh();
425 	}
426 
427 	/**
428 	 * ApplicationListener endpoint that receives events from this servlet's
429 	 * WebApplicationContext.
430 	 * <p>The default implementation calls {@link #onRefresh} in case of a
431 	 * {@link org.springframework.context.event.ContextRefreshedEvent},
432 	 * triggering a refresh of this servlet's context-dependent state.
433 	 * @param event the incoming ApplicationContext event
434 	 */
435 	@Override
436 	public void onApplicationEvent(ContextRefreshedEvent event) {
437 		this.refreshEventReceived = true;
438 		onRefresh(event.getApplicationContext());
439 	}
440 
441 	/**
442 	 * Template method which can be overridden to add portlet-specific refresh work.
443 	 * Called after successful context refresh.
444 	 * <p>This implementation is empty.
445 	 * @param context the current Portlet ApplicationContext
446 	 * @see #refresh()
447 	 */
448 	protected void onRefresh(ApplicationContext context) {
449 		// For subclasses: do nothing by default.
450 	}
451 
452 
453 	/**
454 	 * Overridden for friendlier behavior in unit tests.
455 	 */
456 	@Override
457 	protected String getTitle(RenderRequest renderRequest) {
458 		try {
459 			return super.getTitle(renderRequest);
460 		}
461 		catch (NullPointerException ex) {
462 			return getPortletName();
463 		}
464 	}
465 
466 	/**
467 	 * Delegate action requests to processRequest/doActionService.
468 	 */
469 	@Override
470 	public final void processAction(ActionRequest request, ActionResponse response)
471 			throws PortletException, IOException {
472 
473 		processRequest(request, response);
474 	}
475 
476 	/**
477 	 * Delegate render requests to processRequest/doRenderService.
478 	 */
479 	@Override
480 	protected final void doDispatch(RenderRequest request, RenderResponse response)
481 			throws PortletException, IOException {
482 
483 		processRequest(request, response);
484 	}
485 
486 	@Override
487 	public void serveResource(ResourceRequest request, ResourceResponse response)
488 			throws PortletException, IOException {
489 
490 		processRequest(request, response);
491 	}
492 
493 	@Override
494 	public void processEvent(EventRequest request, EventResponse response)
495 			throws PortletException, IOException {
496 
497 		processRequest(request, response);
498 	}
499 
500 	/**
501 	 * Process this request, publishing an event regardless of the outcome.
502 	 * The actual event handling is performed by the abstract
503 	 * {@code doActionService()} and {@code doRenderService()} template methods.
504 	 * @see #doActionService
505 	 * @see #doRenderService
506 	 */
507 	protected final void processRequest(PortletRequest request, PortletResponse response)
508 			throws PortletException, IOException {
509 
510 		long startTime = System.currentTimeMillis();
511 		Throwable failureCause = null;
512 
513 		// Expose current LocaleResolver and request as LocaleContext.
514 		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
515 		LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);
516 
517 		// Expose current RequestAttributes to current thread.
518 		RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
519 		PortletRequestAttributes requestAttributes = null;
520 		if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(PortletRequestAttributes.class) ||
521 				previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
522 			requestAttributes = new PortletRequestAttributes(request, response);
523 			RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
524 		}
525 
526 		if (logger.isTraceEnabled()) {
527 			logger.trace("Bound request context to thread: " + request);
528 		}
529 
530 		try {
531 			String phase = (String) request.getAttribute(PortletRequest.LIFECYCLE_PHASE);
532 			if (PortletRequest.ACTION_PHASE.equals(phase)) {
533 				doActionService((ActionRequest) request, (ActionResponse) response);
534 			}
535 			else if (PortletRequest.RENDER_PHASE.equals(phase)) {
536 				doRenderService((RenderRequest) request, (RenderResponse) response);
537 			}
538 			else if (PortletRequest.RESOURCE_PHASE.equals(phase)) {
539 				doResourceService((ResourceRequest) request, (ResourceResponse) response);
540 			}
541 			else if (PortletRequest.EVENT_PHASE.equals(phase)) {
542 				doEventService((EventRequest) request, (EventResponse) response);
543 			}
544 			else {
545 				throw new IllegalStateException("Invalid portlet request phase: " + phase);
546 			}
547 		}
548 		catch (PortletException ex) {
549 			failureCause = ex;
550 			throw ex;
551 		}
552 		catch (IOException ex) {
553 			failureCause = ex;
554 			throw ex;
555 		}
556 		catch (Throwable ex) {
557 			failureCause = ex;
558 			throw new PortletException("Request processing failed", ex);
559 		}
560 
561 		finally {
562 			// Clear request attributes and reset thread-bound context.
563 			LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
564 			if (requestAttributes != null) {
565 				RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
566 				requestAttributes.requestCompleted();
567 			}
568 			if (logger.isTraceEnabled()) {
569 				logger.trace("Cleared thread-bound resource request context: " + request);
570 			}
571 
572 			if (failureCause != null) {
573 				logger.error("Could not complete request", failureCause);
574 			}
575 			else {
576 				logger.debug("Successfully completed request");
577 			}
578 			if (this.publishEvents) {
579 				// Whether or not we succeeded, publish an event.
580 				long processingTime = System.currentTimeMillis() - startTime;
581 				this.portletApplicationContext.publishEvent(
582 						new PortletRequestHandledEvent(this,
583 								getPortletConfig().getPortletName(), request.getPortletMode().toString(),
584 								(request instanceof ActionRequest ? "action" : "render"),
585 								request.getRequestedSessionId(), getUsernameForRequest(request),
586 								processingTime, failureCause));
587 			}
588 		}
589 	}
590 
591 	/**
592 	 * Build a LocaleContext for the given request, exposing the request's
593 	 * primary locale as current locale.
594 	 * @param request current HTTP request
595 	 * @return the corresponding LocaleContext
596 	 */
597 	protected LocaleContext buildLocaleContext(PortletRequest request) {
598 		return new SimpleLocaleContext(request.getLocale());
599 	}
600 
601 	/**
602 	 * Determine the username for the given request.
603 	 * <p>The default implementation first tries the UserPrincipal.
604 	 * If that does not exist, then it checks the USER_INFO map.
605 	 * Can be overridden in subclasses.
606 	 * @param request current portlet request
607 	 * @return the username, or {@code null} if none found
608 	 * @see javax.portlet.PortletRequest#getUserPrincipal()
609 	 * @see javax.portlet.PortletRequest#getRemoteUser()
610 	 * @see javax.portlet.PortletRequest#USER_INFO
611 	 * @see #setUserinfoUsernameAttributes
612 	 */
613 	protected String getUsernameForRequest(PortletRequest request) {
614 		// Try the principal.
615 		Principal userPrincipal = request.getUserPrincipal();
616 		if (userPrincipal != null) {
617 			return userPrincipal.getName();
618 		}
619 
620 		// Try the remote user name.
621 		String userName = request.getRemoteUser();
622 		if (userName != null) {
623 			return userName;
624 		}
625 
626 		// Try the Portlet USER_INFO map.
627 		Map<?, ?> userInfo = (Map<?, ?>) request.getAttribute(PortletRequest.USER_INFO);
628 		if (userInfo != null) {
629 			for (int i = 0, n = this.userinfoUsernameAttributes.length; i < n; i++) {
630 				userName = (String) userInfo.get(this.userinfoUsernameAttributes[i]);
631 				if (userName != null) {
632 					return userName;
633 				}
634 			}
635 		}
636 
637 		// Nothing worked...
638 		return null;
639 	}
640 
641 
642 	/**
643 	 * Subclasses must implement this method to do the work of action request handling.
644 	 * <p>The contract is essentially the same as that for the {@code processAction}
645 	 * method of GenericPortlet.
646 	 * <p>This class intercepts calls to ensure that exception handling and
647 	 * event publication takes place.
648 	 * @param request current action request
649 	 * @param response current action response
650 	 * @throws Exception in case of any kind of processing failure
651 	 * @see javax.portlet.GenericPortlet#processAction
652 	 */
653 	protected abstract void doActionService(ActionRequest request, ActionResponse response)
654 			throws Exception;
655 
656 	/**
657 	 * Subclasses must implement this method to do the work of render request handling.
658 	 * <p>The contract is essentially the same as that for the {@code doDispatch}
659 	 * method of GenericPortlet.
660 	 * <p>This class intercepts calls to ensure that exception handling and
661 	 * event publication takes place.
662 	 * @param request current render request
663 	 * @param response current render response
664 	 * @throws Exception in case of any kind of processing failure
665 	 * @see javax.portlet.GenericPortlet#doDispatch
666 	 */
667 	protected abstract void doRenderService(RenderRequest request, RenderResponse response)
668 			throws Exception;
669 
670 	/**
671 	 * Subclasses must implement this method to do the work of resource request handling.
672 	 * <p>The contract is essentially the same as that for the {@code serveResource}
673 	 * method of GenericPortlet.
674 	 * <p>This class intercepts calls to ensure that exception handling and
675 	 * event publication takes place.
676 	 * @param request current resource request
677 	 * @param response current resource response
678 	 * @throws Exception in case of any kind of processing failure
679 	 * @see javax.portlet.GenericPortlet#serveResource
680 	 */
681 	protected abstract void doResourceService(ResourceRequest request, ResourceResponse response)
682 			throws Exception;
683 
684 	/**
685 	 * Subclasses must implement this method to do the work of event request handling.
686 	 * <p>The contract is essentially the same as that for the {@code processEvent}
687 	 * method of GenericPortlet.
688 	 * <p>This class intercepts calls to ensure that exception handling and
689 	 * event publication takes place.
690 	 * @param request current event request
691 	 * @param response current event response
692 	 * @throws Exception in case of any kind of processing failure
693 	 * @see javax.portlet.GenericPortlet#processEvent
694 	 */
695 	protected abstract void doEventService(EventRequest request, EventResponse response)
696 			throws Exception;
697 
698 
699 	/**
700 	 * Close the ApplicationContext of this portlet.
701 	 * @see org.springframework.context.ConfigurableApplicationContext#close()
702 	 */
703 	@Override
704 	public void destroy() {
705 		getPortletContext().log("Destroying Spring FrameworkPortlet '" + getPortletName() + "'");
706 		if (this.portletApplicationContext instanceof ConfigurableApplicationContext) {
707 			((ConfigurableApplicationContext) this.portletApplicationContext).close();
708 		}
709 	}
710 
711 }