001/*
002 * Copyright 2002-2016 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;
018
019import java.util.Collections;
020import java.util.Enumeration;
021import java.util.LinkedHashMap;
022import java.util.Locale;
023import java.util.Map;
024import java.util.ResourceBundle;
025import javax.portlet.ActionRequest;
026import javax.portlet.ActionResponse;
027import javax.portlet.EventPortlet;
028import javax.portlet.EventRequest;
029import javax.portlet.EventResponse;
030import javax.portlet.Portlet;
031import javax.portlet.PortletConfig;
032import javax.portlet.PortletContext;
033import javax.portlet.PortletSession;
034import javax.portlet.RenderRequest;
035import javax.portlet.RenderResponse;
036import javax.portlet.ResourceRequest;
037import javax.portlet.ResourceResponse;
038import javax.portlet.ResourceServingPortlet;
039import javax.xml.XMLConstants;
040import javax.xml.namespace.QName;
041
042import org.springframework.beans.factory.BeanNameAware;
043import org.springframework.beans.factory.DisposableBean;
044import org.springframework.beans.factory.InitializingBean;
045import org.springframework.web.portlet.ModelAndView;
046import org.springframework.web.portlet.NoHandlerFoundException;
047import org.springframework.web.portlet.context.PortletConfigAware;
048import org.springframework.web.portlet.context.PortletContextAware;
049import org.springframework.web.portlet.util.PortletUtils;
050
051/**
052 * {@link Controller} implementation that wraps a portlet instance which it manages
053 * internally. Such a wrapped portlet is not known outside of this controller;
054 * its entire lifecycle is covered here.
055 *
056 * <p>Useful to invoke an existing portlet via Spring's dispatching infrastructure,
057 * for example to apply Spring
058 * {@link org.springframework.web.portlet.HandlerInterceptor HandlerInterceptors}
059 * to its requests.
060 *
061 * <p><b>Example:</b>
062 *
063 * <pre class="code">&lt;bean id="wrappingController" class="org.springframework.web.portlet.mvc.PortletWrappingController"&gt;
064 *   &lt;property name="portletClass"&gt;
065 *     &lt;value&gt;org.springframework.web.portlet.sample.HelloWorldPortlet&lt;/value&gt;
066 *   &lt;/property&gt;
067 *   &lt;property name="portletName"&gt;
068 *     &lt;value&gt;hello-world&lt;/value&gt;
069 *   &lt;/property&gt;
070 *   &lt;property name="initParameters"&gt;
071 *     &lt;props&gt;
072 *       &lt;prop key="config"&gt;/WEB-INF/hello-world-portlet-config.xml&lt;/prop&gt;
073 *     &lt;/props&gt;
074 *   &lt;/property&gt;
075 * &lt;/bean&gt;</pre>
076 *
077 * @author Juergen Hoeller
078 * @author John A. Lewis
079 * @since 2.0
080 */
081public class PortletWrappingController extends AbstractController
082                implements ResourceAwareController, EventAwareController,
083                BeanNameAware, InitializingBean, DisposableBean, PortletContextAware, PortletConfigAware {
084
085        private boolean useSharedPortletConfig = true;
086
087        private PortletContext portletContext;
088
089        private PortletConfig portletConfig;
090
091        private Class<?> portletClass;
092
093        private String portletName;
094
095        private Map<String, String> initParameters = new LinkedHashMap<String, String>();
096
097        private String beanName;
098
099        private Portlet portletInstance;
100
101
102        /**
103         * Set whether to use the shared PortletConfig object passed in
104         * through {@code setPortletConfig}, if available.
105         * <p>Default is "true". Turn this setting to "false" to pass in
106         * a mock PortletConfig object with the bean name as portlet name,
107         * holding the current PortletContext.
108         * @see #setPortletConfig
109         */
110        public void setUseSharedPortletConfig(boolean useSharedPortletConfig) {
111                this.useSharedPortletConfig = useSharedPortletConfig;
112        }
113
114        @Override
115        public void setPortletContext(PortletContext portletContext) {
116                this.portletContext = portletContext;
117        }
118
119        @Override
120        public void setPortletConfig(PortletConfig portletConfig) {
121                this.portletConfig = portletConfig;
122        }
123
124        /**
125         * Set the class of the Portlet to wrap.
126         * Needs to implement {@code javax.portlet.Portlet}.
127         * @see javax.portlet.Portlet
128         */
129        public void setPortletClass(Class<?> portletClass) {
130                this.portletClass = portletClass;
131        }
132
133        /**
134         * Set the name of the Portlet to wrap.
135         * Default is the bean name of this controller.
136         */
137        public void setPortletName(String portletName) {
138                this.portletName = portletName;
139        }
140
141        /**
142         * Specify init parameters for the portlet to wrap,
143         * as name-value pairs.
144         */
145        public void setInitParameters(Map<String, String> initParameters) {
146                this.initParameters = initParameters;
147        }
148
149        @Override
150        public void setBeanName(String name) {
151                this.beanName = name;
152        }
153
154
155        @Override
156        public void afterPropertiesSet() throws Exception {
157                if (this.portletClass == null) {
158                        throw new IllegalArgumentException("portletClass is required");
159                }
160                if (!Portlet.class.isAssignableFrom(this.portletClass)) {
161                        throw new IllegalArgumentException("portletClass [" + this.portletClass.getName() +
162                                "] needs to implement interface [javax.portlet.Portlet]");
163                }
164                if (this.portletName == null) {
165                        this.portletName = this.beanName;
166                }
167                PortletConfig config = this.portletConfig;
168                if (config == null || !this.useSharedPortletConfig) {
169                        config = new DelegatingPortletConfig();
170                }
171                this.portletInstance = (Portlet) this.portletClass.newInstance();
172                this.portletInstance.init(config);
173        }
174
175
176        @Override
177        protected void handleActionRequestInternal(
178                        ActionRequest request, ActionResponse response) throws Exception {
179
180                this.portletInstance.processAction(request, response);
181        }
182
183        @Override
184        protected ModelAndView handleRenderRequestInternal(
185                        RenderRequest request, RenderResponse response) throws Exception {
186
187                this.portletInstance.render(request, response);
188                return null;
189        }
190
191        @Override
192        public ModelAndView handleResourceRequest(
193                        ResourceRequest request, ResourceResponse response) throws Exception {
194
195                if (!(this.portletInstance instanceof ResourceServingPortlet)) {
196                        throw new NoHandlerFoundException("Cannot handle resource request - target portlet [" +
197                                        this.portletInstance.getClass() + " does not implement ResourceServingPortlet");
198                }
199                ResourceServingPortlet resourcePortlet = (ResourceServingPortlet) this.portletInstance;
200
201                // Delegate to PortletContentGenerator for checking and preparing.
202                checkAndPrepare(request, response);
203
204                // Execute in synchronized block if required.
205                if (isSynchronizeOnSession()) {
206                        PortletSession session = request.getPortletSession(false);
207                        if (session != null) {
208                                Object mutex = PortletUtils.getSessionMutex(session);
209                                synchronized (mutex) {
210                                        resourcePortlet.serveResource(request, response);
211                                        return null;
212                                }
213                        }
214                }
215
216                resourcePortlet.serveResource(request, response);
217                return null;
218        }
219
220        @Override
221        public void handleEventRequest(
222                        EventRequest request, EventResponse response) throws Exception {
223
224                if (!(this.portletInstance instanceof EventPortlet)) {
225                        logger.debug("Ignoring event request for non-event target portlet: " + this.portletInstance.getClass());
226                        return;
227                }
228                EventPortlet eventPortlet = (EventPortlet) this.portletInstance;
229
230                // Delegate to PortletContentGenerator for checking and preparing.
231                check(request, response);
232
233                // Execute in synchronized block if required.
234                if (isSynchronizeOnSession()) {
235                        PortletSession session = request.getPortletSession(false);
236                        if (session != null) {
237                                Object mutex = PortletUtils.getSessionMutex(session);
238                                synchronized (mutex) {
239                                        eventPortlet.processEvent(request, response);
240                                        return;
241                                }
242                        }
243                }
244
245                eventPortlet.processEvent(request, response);
246        }
247
248
249        @Override
250        public void destroy() {
251                this.portletInstance.destroy();
252        }
253
254
255        /**
256         * Internal implementation of the PortletConfig interface, to be passed
257         * to the wrapped portlet.
258         * <p>Delegates to {@link PortletWrappingController} fields
259         * and methods to provide init parameters and other environment info.
260         */
261        private class DelegatingPortletConfig implements PortletConfig {
262
263                @Override
264                public String getPortletName() {
265                        return portletName;
266                }
267
268                @Override
269                public PortletContext getPortletContext() {
270                        return portletContext;
271                }
272
273                @Override
274                public String getInitParameter(String paramName) {
275                        return initParameters.get(paramName);
276                }
277
278                @Override
279                public Enumeration<String> getInitParameterNames() {
280                        return Collections.enumeration(initParameters.keySet());
281                }
282
283                @Override
284                public ResourceBundle getResourceBundle(Locale locale) {
285                        return (portletConfig != null ? portletConfig.getResourceBundle(locale) : null);
286                }
287
288                @Override
289                public Enumeration<String> getPublicRenderParameterNames() {
290                        return Collections.enumeration(Collections.<String>emptySet());
291                }
292
293                @Override
294                public String getDefaultNamespace() {
295                        return XMLConstants.NULL_NS_URI;
296                }
297
298                @Override
299                public Enumeration<QName> getPublishingEventQNames() {
300                        return Collections.enumeration(Collections.<QName>emptySet());
301                }
302
303                @Override
304                public Enumeration<QName> getProcessingEventQNames() {
305                        return Collections.enumeration(Collections.<QName>emptySet());
306                }
307
308                @Override
309                public Enumeration<Locale> getSupportedLocales() {
310                        return Collections.enumeration(Collections.<Locale>emptySet());
311                }
312
313                @Override
314                public Map<String, String[]> getContainerRuntimeOptions() {
315                        return (portletConfig != null ? portletConfig.getContainerRuntimeOptions() : null);
316                }
317        }
318
319}