001/*
002 * Copyright 2002-2012 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.util.Enumeration;
020import java.util.HashSet;
021import java.util.Set;
022import javax.portlet.GenericPortlet;
023import javax.portlet.PortletConfig;
024import javax.portlet.PortletContext;
025import javax.portlet.PortletException;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.springframework.beans.BeanWrapper;
031import org.springframework.beans.BeansException;
032import org.springframework.beans.MutablePropertyValues;
033import org.springframework.beans.PropertyAccessorFactory;
034import org.springframework.beans.PropertyValue;
035import org.springframework.beans.PropertyValues;
036import org.springframework.context.EnvironmentAware;
037import org.springframework.core.env.ConfigurableEnvironment;
038import org.springframework.core.env.Environment;
039import org.springframework.core.env.EnvironmentCapable;
040import org.springframework.core.io.Resource;
041import org.springframework.core.io.ResourceEditor;
042import org.springframework.core.io.ResourceLoader;
043import org.springframework.util.Assert;
044import org.springframework.util.StringUtils;
045import org.springframework.web.portlet.context.PortletContextResourceLoader;
046import org.springframework.web.portlet.context.StandardPortletEnvironment;
047
048/**
049 * Simple extension of {@code javax.portlet.GenericPortlet} that treats
050 * its config parameters as bean properties.
051 *
052 * <p>A very handy superclass for any type of portlet. Type conversion is automatic.
053 * It is also possible for subclasses to specify required properties.
054 *
055 * <p>This portlet leaves request handling to subclasses, inheriting the default
056 * behaviour of GenericPortlet ({@code doDispatch}, {@code processAction}, etc).
057 *
058 * <p>This portlet superclass has no dependency on a Spring application context,
059 * in contrast to the FrameworkPortlet class which loads its own context.
060 *
061 * @author William G. Thompson, Jr.
062 * @author John A. Lewis
063 * @author Juergen Hoeller
064 * @since 2.0
065 * @see #addRequiredProperty
066 * @see #initPortletBean
067 * @see #doDispatch
068 * @see #processAction
069 * @see FrameworkPortlet
070 */
071public abstract class GenericPortletBean extends GenericPortlet
072                implements EnvironmentCapable, EnvironmentAware {
073
074        /** Logger available to subclasses */
075        protected final Log logger = LogFactory.getLog(getClass());
076
077        /**
078         * Set of required properties (Strings) that must be supplied as
079         * config parameters to this portlet.
080         */
081        private final Set<String> requiredProperties = new HashSet<String>();
082
083        private ConfigurableEnvironment environment;
084
085
086        /**
087         * Subclasses can invoke this method to specify that this property
088         * (which must match a JavaBean property they expose) is mandatory,
089         * and must be supplied as a config parameter. This method would
090         * normally be called from a subclass constructor.
091         * @param property name of the required property
092         */
093        protected final void addRequiredProperty(String property) {
094                this.requiredProperties.add(property);
095        }
096
097        /**
098         * Map config parameters onto bean properties of this portlet, and
099         * invoke subclass initialization.
100         * @throws PortletException if bean properties are invalid (or required
101         * properties are missing), or if subclass initialization fails.
102         */
103        @Override
104        public final void init() throws PortletException {
105                if (logger.isInfoEnabled()) {
106                        logger.info("Initializing portlet '" + getPortletName() + "'");
107                }
108
109                // Set bean properties from init parameters.
110                try {
111                        PropertyValues pvs = new PortletConfigPropertyValues(getPortletConfig(), this.requiredProperties);
112                        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
113                        ResourceLoader resourceLoader = new PortletContextResourceLoader(getPortletContext());
114                        bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
115                        initBeanWrapper(bw);
116                        bw.setPropertyValues(pvs, true);
117                }
118                catch (BeansException ex) {
119                        logger.error("Failed to set bean properties on portlet '" + getPortletName() + "'", ex);
120                        throw ex;
121                }
122
123                // let subclasses do whatever initialization they like
124                initPortletBean();
125
126                if (logger.isInfoEnabled()) {
127                        logger.info("Portlet '" + getPortletName() + "' configured successfully");
128                }
129        }
130
131        /**
132         * Initialize the BeanWrapper for this GenericPortletBean,
133         * possibly with custom editors.
134         * @param bw the BeanWrapper to initialize
135         * @throws BeansException if thrown by BeanWrapper methods
136         * @see org.springframework.beans.BeanWrapper#registerCustomEditor
137         */
138        protected void initBeanWrapper(BeanWrapper bw) throws BeansException {
139        }
140
141
142        /**
143         * Overridden method that simply returns {@code null} when no
144         * PortletConfig set yet.
145         * @see #getPortletConfig()
146         */
147        @Override
148        public final String getPortletName() {
149                return (getPortletConfig() != null ? getPortletConfig().getPortletName() : null);
150        }
151
152        /**
153         * Overridden method that simply returns {@code null} when no
154         * PortletConfig set yet.
155         * @see #getPortletConfig()
156         */
157        @Override
158        public final PortletContext getPortletContext() {
159                return (getPortletConfig() != null ? getPortletConfig().getPortletContext() : null);
160        }
161
162
163        /**
164         * Subclasses may override this to perform custom initialization.
165         * All bean properties of this portlet will have been set before this
166         * method is invoked. This default implementation does nothing.
167         * @throws PortletException if subclass initialization fails
168         */
169        protected void initPortletBean() throws PortletException {
170        }
171
172        /**
173         * {@inheritDoc}
174         * @throws IllegalArgumentException if environment is not assignable to
175         * {@code ConfigurableEnvironment}.
176         */
177        @Override
178        public void setEnvironment(Environment environment) {
179                Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
180                this.environment = (ConfigurableEnvironment)environment;
181        }
182
183        /**
184         * {@inheritDoc}
185         * <p>If {@code null}, a new environment will be initialized via
186         * {@link #createEnvironment()}.
187         */
188        @Override
189        public ConfigurableEnvironment getEnvironment() {
190                if (this.environment == null) {
191                        this.environment = this.createEnvironment();
192                }
193                return this.environment;
194        }
195
196        /**
197         * Create and return a new {@link StandardPortletEnvironment}. Subclasses may override
198         * in order to configure the environment or specialize the environment type returned.
199         */
200        protected ConfigurableEnvironment createEnvironment() {
201                return new StandardPortletEnvironment();
202        }
203
204
205        /**
206         * PropertyValues implementation created from PortletConfig init parameters.
207         */
208        @SuppressWarnings("serial")
209        private static class PortletConfigPropertyValues extends MutablePropertyValues {
210
211                /**
212                 * Create new PortletConfigPropertyValues.
213                 * @param config PortletConfig we'll use to take PropertyValues from
214                 * @param requiredProperties set of property names we need, where
215                 * we can't accept default values
216                 * @throws PortletException if any required properties are missing
217                 */
218                private PortletConfigPropertyValues(PortletConfig config, Set<String> requiredProperties)
219                        throws PortletException {
220
221                        Set<String> missingProps = (requiredProperties != null && !requiredProperties.isEmpty()) ?
222                                        new HashSet<String>(requiredProperties) : null;
223
224                        Enumeration<String> en = config.getInitParameterNames();
225                        while (en.hasMoreElements()) {
226                                String property = en.nextElement();
227                                Object value = config.getInitParameter(property);
228                                addPropertyValue(new PropertyValue(property, value));
229                                if (missingProps != null) {
230                                        missingProps.remove(property);
231                                }
232                        }
233
234                        // fail if we are still missing properties
235                        if (missingProps != null && missingProps.size() > 0) {
236                                throw new PortletException(
237                                        "Initialization from PortletConfig for portlet '" + config.getPortletName() +
238                                        "' failed; the following required properties were missing: " +
239                                        StringUtils.collectionToDelimitedString(missingProps, ", "));
240                        }
241                }
242        }
243
244}