001/*
002 * Copyright 2002-2015 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;
018
019import java.io.IOException;
020import java.security.Principal;
021import java.util.Map;
022import javax.portlet.ActionRequest;
023import javax.portlet.ActionResponse;
024import javax.portlet.EventRequest;
025import javax.portlet.EventResponse;
026import javax.portlet.PortletException;
027import javax.portlet.PortletRequest;
028import javax.portlet.PortletResponse;
029import javax.portlet.RenderRequest;
030import javax.portlet.RenderResponse;
031import javax.portlet.ResourceRequest;
032import javax.portlet.ResourceResponse;
033
034import org.springframework.beans.BeanUtils;
035import org.springframework.context.ApplicationContext;
036import org.springframework.context.ApplicationContextException;
037import org.springframework.context.ApplicationListener;
038import org.springframework.context.ConfigurableApplicationContext;
039import org.springframework.context.event.ContextRefreshedEvent;
040import org.springframework.context.event.SourceFilteringListener;
041import org.springframework.context.i18n.LocaleContext;
042import org.springframework.context.i18n.LocaleContextHolder;
043import org.springframework.context.i18n.SimpleLocaleContext;
044import org.springframework.core.env.ConfigurableEnvironment;
045import org.springframework.web.context.request.RequestAttributes;
046import org.springframework.web.context.request.RequestContextHolder;
047import org.springframework.web.context.request.ServletRequestAttributes;
048import org.springframework.web.portlet.context.ConfigurablePortletApplicationContext;
049import org.springframework.web.portlet.context.PortletApplicationContextUtils;
050import org.springframework.web.portlet.context.PortletRequestAttributes;
051import org.springframework.web.portlet.context.PortletRequestHandledEvent;
052import org.springframework.web.portlet.context.StandardPortletEnvironment;
053import org.springframework.web.portlet.context.XmlPortletApplicationContext;
054
055/**
056 * Base portlet for Spring's portlet framework. Provides integration with
057 * a Spring application context, in a JavaBean-based overall solution.
058 *
059 * <p>This class offers the following functionality:
060 * <ul>
061 * <li>Manages a Portlet {@link org.springframework.context.ApplicationContext}
062 * instance per portlet. The portlet's configuration is determined by beans
063 * in the portlet's namespace.
064 * <li>Publishes events on request processing, whether or not a request is
065 * successfully handled.
066 * </ul>
067 *
068 * <p>Subclasses must implement {@link #doActionService} and {@link #doRenderService}
069 * to handle action and render requests. Because this extends {@link GenericPortletBean}
070 * rather than Portlet directly, bean properties are mapped onto it. Subclasses can
071 * override {@link #initFrameworkPortlet()} for custom initialization.
072 *
073 * <p>Regards a "contextClass" parameter at the portlet init-param level,
074 * falling back to the default context class
075 * ({@link org.springframework.web.portlet.context.XmlPortletApplicationContext})
076 * if not found. Note that, with the default FrameworkPortlet,
077 * a context class needs to implement the
078 * {@link org.springframework.web.portlet.context.ConfigurablePortletApplicationContext} SPI.
079 *
080 * <p>Passes a "contextConfigLocation" portlet init-param to the context instance,
081 * parsing it into potentially multiple file paths which can be separated by any
082 * number of commas and spaces, like "test-portlet.xml, myPortlet.xml".
083 * If not explicitly specified, the context implementation is supposed to build a
084 * default location from the namespace of the portlet.
085 *
086 * <p>Note: In case of multiple config locations, later bean definitions will
087 * override ones defined in earlier loaded files, at least when using one of
088 * Spring's default ApplicationContext implementations. This can be leveraged
089 * to deliberately override certain bean definitions via an extra XML file.
090 *
091 * <p>The default namespace is "'portlet-name'-portlet", e.g. "test-portlet" for a
092 * portlet-name "test" (leading to a "/WEB-INF/test-portlet.xml" default location
093 * with XmlPortletApplicationContext). The namespace can also be set explicitly via
094 * the "namespace" portlet init-param.
095 *
096 * @author William G. Thompson, Jr.
097 * @author John A. Lewis
098 * @author Juergen Hoeller
099 * @since 2.0
100 * @see #doActionService
101 * @see #doRenderService
102 * @see #setContextClass
103 * @see #setContextConfigLocation
104 * @see #setNamespace
105 */
106public 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 ||
521                                PortletRequestAttributes.class == previousRequestAttributes.getClass() ||
522                                ServletRequestAttributes.class == previousRequestAttributes.getClass()) {
523                        requestAttributes = new PortletRequestAttributes(request, response);
524                        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
525                }
526
527                if (logger.isTraceEnabled()) {
528                        logger.trace("Bound request context to thread: " + request);
529                }
530
531                try {
532                        String phase = (String) request.getAttribute(PortletRequest.LIFECYCLE_PHASE);
533                        if (PortletRequest.ACTION_PHASE.equals(phase)) {
534                                doActionService((ActionRequest) request, (ActionResponse) response);
535                        }
536                        else if (PortletRequest.RENDER_PHASE.equals(phase)) {
537                                doRenderService((RenderRequest) request, (RenderResponse) response);
538                        }
539                        else if (PortletRequest.RESOURCE_PHASE.equals(phase)) {
540                                doResourceService((ResourceRequest) request, (ResourceResponse) response);
541                        }
542                        else if (PortletRequest.EVENT_PHASE.equals(phase)) {
543                                doEventService((EventRequest) request, (EventResponse) response);
544                        }
545                        else {
546                                throw new IllegalStateException("Invalid portlet request phase: " + phase);
547                        }
548                }
549                catch (PortletException ex) {
550                        failureCause = ex;
551                        throw ex;
552                }
553                catch (IOException ex) {
554                        failureCause = ex;
555                        throw ex;
556                }
557                catch (Throwable ex) {
558                        failureCause = ex;
559                        throw new PortletException("Request processing failed", ex);
560                }
561
562                finally {
563                        // Clear request attributes and reset thread-bound context.
564                        LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
565                        if (requestAttributes != null) {
566                                RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
567                                requestAttributes.requestCompleted();
568                        }
569                        if (logger.isTraceEnabled()) {
570                                logger.trace("Cleared thread-bound resource request context: " + request);
571                        }
572
573                        if (failureCause != null) {
574                                logger.error("Could not complete request", failureCause);
575                        }
576                        else {
577                                logger.debug("Successfully completed request");
578                        }
579                        if (this.publishEvents) {
580                                // Whether or not we succeeded, publish an event.
581                                long processingTime = System.currentTimeMillis() - startTime;
582                                this.portletApplicationContext.publishEvent(
583                                                new PortletRequestHandledEvent(this,
584                                                                getPortletConfig().getPortletName(), request.getPortletMode().toString(),
585                                                                (request instanceof ActionRequest ? "action" : "render"),
586                                                                request.getRequestedSessionId(), getUsernameForRequest(request),
587                                                                processingTime, failureCause));
588                        }
589                }
590        }
591
592        /**
593         * Build a LocaleContext for the given request, exposing the request's
594         * primary locale as current locale.
595         * @param request current HTTP request
596         * @return the corresponding LocaleContext
597         */
598        protected LocaleContext buildLocaleContext(PortletRequest request) {
599                return new SimpleLocaleContext(request.getLocale());
600        }
601
602        /**
603         * Determine the username for the given request.
604         * <p>The default implementation first tries the UserPrincipal.
605         * If that does not exist, then it checks the USER_INFO map.
606         * Can be overridden in subclasses.
607         * @param request current portlet request
608         * @return the username, or {@code null} if none found
609         * @see javax.portlet.PortletRequest#getUserPrincipal()
610         * @see javax.portlet.PortletRequest#getRemoteUser()
611         * @see javax.portlet.PortletRequest#USER_INFO
612         * @see #setUserinfoUsernameAttributes
613         */
614        protected String getUsernameForRequest(PortletRequest request) {
615                // Try the principal.
616                Principal userPrincipal = request.getUserPrincipal();
617                if (userPrincipal != null) {
618                        return userPrincipal.getName();
619                }
620
621                // Try the remote user name.
622                String userName = request.getRemoteUser();
623                if (userName != null) {
624                        return userName;
625                }
626
627                // Try the Portlet USER_INFO map.
628                Map<?, ?> userInfo = (Map<?, ?>) request.getAttribute(PortletRequest.USER_INFO);
629                if (userInfo != null) {
630                        for (int i = 0, n = this.userinfoUsernameAttributes.length; i < n; i++) {
631                                userName = (String) userInfo.get(this.userinfoUsernameAttributes[i]);
632                                if (userName != null) {
633                                        return userName;
634                                }
635                        }
636                }
637
638                // Nothing worked...
639                return null;
640        }
641
642
643        /**
644         * Subclasses must implement this method to do the work of action request handling.
645         * <p>The contract is essentially the same as that for the {@code processAction}
646         * method of GenericPortlet.
647         * <p>This class intercepts calls to ensure that exception handling and
648         * event publication takes place.
649         * @param request current action request
650         * @param response current action response
651         * @throws Exception in case of any kind of processing failure
652         * @see javax.portlet.GenericPortlet#processAction
653         */
654        protected abstract void doActionService(ActionRequest request, ActionResponse response)
655                        throws Exception;
656
657        /**
658         * Subclasses must implement this method to do the work of render request handling.
659         * <p>The contract is essentially the same as that for the {@code doDispatch}
660         * method of GenericPortlet.
661         * <p>This class intercepts calls to ensure that exception handling and
662         * event publication takes place.
663         * @param request current render request
664         * @param response current render response
665         * @throws Exception in case of any kind of processing failure
666         * @see javax.portlet.GenericPortlet#doDispatch
667         */
668        protected abstract void doRenderService(RenderRequest request, RenderResponse response)
669                        throws Exception;
670
671        /**
672         * Subclasses must implement this method to do the work of resource request handling.
673         * <p>The contract is essentially the same as that for the {@code serveResource}
674         * method of GenericPortlet.
675         * <p>This class intercepts calls to ensure that exception handling and
676         * event publication takes place.
677         * @param request current resource request
678         * @param response current resource response
679         * @throws Exception in case of any kind of processing failure
680         * @see javax.portlet.GenericPortlet#serveResource
681         */
682        protected abstract void doResourceService(ResourceRequest request, ResourceResponse response)
683                        throws Exception;
684
685        /**
686         * Subclasses must implement this method to do the work of event request handling.
687         * <p>The contract is essentially the same as that for the {@code processEvent}
688         * method of GenericPortlet.
689         * <p>This class intercepts calls to ensure that exception handling and
690         * event publication takes place.
691         * @param request current event request
692         * @param response current event response
693         * @throws Exception in case of any kind of processing failure
694         * @see javax.portlet.GenericPortlet#processEvent
695         */
696        protected abstract void doEventService(EventRequest request, EventResponse response)
697                        throws Exception;
698
699
700        /**
701         * Close the ApplicationContext of this portlet.
702         * @see org.springframework.context.ConfigurableApplicationContext#close()
703         */
704        @Override
705        public void destroy() {
706                getPortletContext().log("Destroying Spring FrameworkPortlet '" + getPortletName() + "'");
707                if (this.portletApplicationContext instanceof ConfigurableApplicationContext) {
708                        ((ConfigurableApplicationContext) this.portletApplicationContext).close();
709                }
710        }
711
712}