001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution, if
019: * any, must include the following acknowlegement:
020: * "This product includes software developed by the
021: * Caucho Technology (http://www.caucho.com/)."
022: * Alternately, this acknowlegement may appear in the software itself,
023: * if and wherever such third-party acknowlegements normally appear.
024: *
025: * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
026: * endorse or promote products derived from this software without prior
027: * written permission. For written permission, please contact
028: * info@caucho.com.
029: *
030: * 5. Products derived from this software may not be called "Resin"
031: * nor may "Resin" appear in their names without prior written
032: * permission of Caucho Technology.
033: *
034: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
038: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
039: * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
040: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
041: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
042: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
043: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
044: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045: *
046: * @author Sam
047: */
048:
049: package com.caucho.portal.generic;
050:
051: import javax.portlet.*;
052: import javax.servlet.ServletConfig;
053: import javax.servlet.ServletException;
054: import javax.servlet.http.HttpServlet;
055: import javax.servlet.http.HttpServletRequest;
056: import javax.servlet.http.HttpServletResponse;
057: import java.io.IOException;
058: import java.lang.reflect.Constructor;
059: import java.lang.reflect.Modifier;
060: import java.util.*;
061: import java.util.logging.Logger;
062:
063: /**
064: * A servlet that uses a class implementing javax.portlet.Portlet.
065: *
066: * This servlet supports the following configuration items.
067: * Items marked with a * can be set as init-param.
068: *
069: * <dl>
070: * <dd>portal
071: * <dt>an instance of {@link Portal}, default is an instance of
072: * {@link GenericPortal}.
073: * <dt>portal-class*
074: * <dd>a class name, an alternative to <code>portal</code>
075: * <dt>portal-ref*
076: * <dd>an attribute name to use for a lookup for a {@link Portal} as
077: * an application attribute. This is an alternative to using
078: * <code>portal</code> and is useful when more than one portlet
079: * servlet share a Portal.
080: * <dt>portlet
081: * <dd>an instance of {@link javax.portlet.Portlet}, required
082: * <dt>portlet-class*
083: * <dd>a class name, an alternative to <code>portlet</code>
084: * <dt>portlet-name*
085: * <dd>a name for the value of
086: * {@link javax.portlet.PortletConfig#getPortletName()},
087: * the default is ServletConfig.getServletName()
088: * <dt>portlet-preferences
089: * <dd>Set the default preferences for the portlet
090: * (see <a href="#portlet-preferences">"Portlet Preferences"</a>)
091: * <dt>expiration-cache*
092: * <dd>an integer, a cache time in seconds for the portlet, 0 (the default)
093: * disables and -1 means forever
094: * <dt>private*
095: * <dd>"true" or "false", if true the response is marked as private for
096: * the client, no cache will share the response with other users
097: * <dt>supported-locales*
098: * A comma-separated list of locales of the form "en-us" or "en_us', the
099: * default is to support all locales.
100: * <dt>resource-bundle*
101: * <dd>the name of a resource bundle, the portlet may use the resource bundle
102: * with {@link javax.portlet.PortletConfig#getResourceBundle(Locale)}
103: * <dt>renderer
104: * <dd>an instance of {@link Renderer}, used to add decorations like headers
105: * footers and controls to the portlet, the default is no renderer. See
106: * {@link AbstractRenderer}.
107: * <dt>renderer-class*
108: * <dd>a class name, an alternative to <code>renderer</code>
109: * {@link #setRenderer(Renderer)}.
110: * </dl>
111: *
112: * <h3><a name="portlet-preferences">Portlet Preferences</a></h3>
113: *
114: * <pre>
115: * <servlet servlet-name="portal"
116: * servlet-class="com.caucho.portal.generic.PortletServlet">
117: *
118: * <init>
119: *
120: * ...
121: *
122: * <portlet-preferences>
123: * <preference name="colour" value="green" read-only="true"/>
124: * or
125: * <preference>
126: * <name>colour</name>
127: * <value>green</value>
128: * <read-only>true</read-only>
129: * </preference>
130: * </portlet-preferences>
131: *
132: * ...
133: * </init>
134: * </servlet>
135: * </pre>
136: */
137: public class PortletServlet extends HttpServlet implements Window,
138: PortletConfig {
139: static protected final Logger log = Logger
140: .getLogger(PortletServlet.class.getName());
141:
142: private Portal _portal;
143: private HttpPortletContext _portletContext;
144:
145: private Portlet _portlet;
146: private String _portletName;
147: private String _namespace = "";
148: private Map<String, String> _initParamMap;
149: private int _expirationCache;
150: private boolean _isPrivate;
151: private Renderer _renderer;
152: private int _bufferSize;
153: private Set<Locale> _supportedLocales;
154: private GenericPortletPreferences _defaultPreferences;
155: private ResourceBundleFactory _resourceBundleFactory;
156:
157: /**
158: * Default is an instance of {@link GenericPortal}.
159: */
160: public void setPortal(Portal portal) {
161: if (_portal != null)
162: throw new IllegalArgumentException("`portal' already set");
163:
164: _portal = portal;
165: }
166:
167: /**
168: * An alternative to {@link #setPortal(Portal)}, specify the class
169: * name of an object to instantiate
170: */
171: public void setPortalClass(String className) {
172: setPortal((Portal) newInstance(Portal.class, className));
173: }
174:
175: /**
176: * An alternative to {@link #setPortal(Portal)}, specify the name
177: * of an attribute to lookup in the ServletContext. This is useful
178: * for sharing a Portal amongst different servlets.
179: */
180: public void setPortalRef(String attributeName) {
181: Portal portal = (Portal) getServletContext().getAttribute(
182: attributeName);
183:
184: if (portal == null)
185: throw new IllegalArgumentException(
186: "Portal not found with ServletContext attribute name `"
187: + attributeName + "'");
188:
189: setPortal(portal);
190: }
191:
192: /**
193: * The namespace is used to uniquely identify this usage of the portlet,
194: * the default is "" (the empty string).
195: *
196: * The namespace is important when using a portlet preferences store,
197: * and also has an effect on the encoding of parameters.
198: */
199: public void setNamespace(String namespace) {
200: _namespace = namespace;
201: }
202:
203: /**
204: * The portlet, required. This method can be called in derived classes,
205: * or through the use of depndency injection on containers that support it,
206: * or indirectly using the init param `portlet-class'.
207: */
208: public void setPortlet(Portlet portlet) {
209: if (_portlet != null)
210: throw new IllegalArgumentException(
211: "`portlet' is already set");
212:
213: _portlet = portlet;
214: }
215:
216: /**
217: * An alternative to {@link #setPortlet(Portlet)}, specify the class
218: * name of an object to instantiate
219: */
220: public void setPortletClass(String className) {
221: setPortlet((Portlet) newInstance(Portlet.class, className));
222: }
223:
224: /**
225: * The default is the value of ServletConfig.getServletName()
226: */
227: public void setPortletName(String portletName) {
228: _portletName = portletName;
229: }
230:
231: /**
232: * Add an init-param for the portlet. If no init-param are added, the
233: * default behaviour is to expose the Servlet's init-param to the portlet.
234: * If this method is called at least once, the Servlet's init-param are not
235: * exposed to the portlet.
236: */
237: public void addInitParam(String name, String value) {
238: if (_initParamMap == null)
239: _initParamMap = new LinkedHashMap<String, String>();
240:
241: _initParamMap.put(name, value);
242: }
243:
244: public void addInitParam(NameValuePair nameValuePair) {
245: addInitParam(nameValuePair.getName(), nameValuePair.getValue());
246: }
247:
248: /**
249: * Set the default preferences.
250: */
251: public void setPortletPreferences(
252: GenericPortletPreferences defaultPreferences) {
253: _defaultPreferences = defaultPreferences;
254: }
255:
256: /**
257: * Enable caching of the response and set the expiration time in seconds. 0
258: * (the default) means do not cache, -1 means unlimited cach time, any other
259: * number is the number of seconds for which the response can be cached.
260: *
261: * Can also be specified with
262: * <a href="#init-param">init-param</a> `expiration-cache'.
263: */
264: public void setExpirationCache(int expirationCache) {
265: _expirationCache = expirationCache;
266: }
267:
268: /**
269: * If true then the response is private, indicating that it contains
270: * information that should only be provided to the current client, default is
271: * false. Setting this to true has an effect on caching, if true then a
272: * cached value
273: * cannot be shared amongst different users and the effectiveness of caching
274: * is greatly reduced.
275: *
276: * Can also be specified with
277: * <a href="#init-param">init-param</a> `private'.
278: */
279: public void setPrivate(boolean isPrivate) {
280: _isPrivate = isPrivate;
281: }
282:
283: /**
284: * Add a supported locale, the default is to support all locales.
285: * This is an ordered list, those added first are more preferrable.
286: */
287: void addSupportedLocale(String locale) {
288: String language = "";
289: String country = "";
290: String variant = "";
291:
292: String[] split = locale.split("_", 3);
293: int len = split.length;
294:
295: if (len == 0) {
296: split = locale.split("-", 3);
297: len = split.length;
298: }
299:
300: if (len == 0)
301: throw new IllegalArgumentException(locale);
302:
303: language = split[0];
304: if (len > 0)
305: country = split[1];
306: if (len > 1)
307: country = split[2];
308:
309: if (_supportedLocales == null)
310: _supportedLocales = new LinkedHashSet<Locale>();
311:
312: _supportedLocales.add(new Locale(language, country, variant));
313: }
314:
315: /**
316: * Add supported locales with a comma separated list, the default is to
317: * support all locales. This is an ordered list, those added first are more
318: * preferrable.
319: */
320: void addSupportedLocales(String locales) {
321: String[] split = locales.split("\\s*,\\s*");
322: for (int i = 0; i < split.length; i++)
323: addSupportedLocale(split[i]);
324: }
325:
326: /**
327: * Set a resource bundle name, used to instantiate an instance of
328: * ResourceBundleFactory.
329: */
330: public void setResourceBundle(String name) {
331: ResourceBundleFactory resourceBundleFactory = new ResourceBundleFactory();
332:
333: resourceBundleFactory.setName(name);
334:
335: setResourceBundleFactory(resourceBundleFactory);
336: }
337:
338: public void setResourceBundleFactory(ResourceBundleFactory factory) {
339: if (_resourceBundleFactory != null)
340: throw new IllegalArgumentException(
341: "resource-bundle-factory already set");
342: }
343:
344: /**
345: * A Renderer wraps decorations around the portlet, see
346: * {@link AbstractRenderer}.
347: */
348: public void setRenderer(Renderer renderer) {
349: _renderer = renderer;
350: }
351:
352: /**
353: * An alternative to {@link #setRenderer(Renderer)}, specify the class
354: * name of an object to instantiate
355: */
356: public void setRendererClass(String className) {
357: setRenderer((Renderer) newInstance(Renderer.class, className));
358: }
359:
360: /**
361: * Default is 0
362: */
363: public void setBufferSize(int bufferSize) {
364: _bufferSize = bufferSize;
365: }
366:
367: public void init(ServletConfig servletConfig)
368: throws ServletException {
369: super .init(servletConfig);
370:
371: String p;
372:
373: p = super .getInitParameter("portal-class");
374: if (p != null)
375: setPortalClass(p);
376:
377: p = super .getInitParameter("portal-ref");
378: if (p != null)
379: setPortalRef(p);
380:
381: if (_portal == null)
382: _portal = new GenericPortal();
383:
384: p = super .getInitParameter("portlet-class");
385: if (p != null)
386: setPortletClass(p);
387:
388: if (_portlet == null)
389: throw new ServletException("`portlet' is required");
390:
391: p = super .getInitParameter("portlet-name");
392: if (p != null)
393: setPortletName(p);
394:
395: if (_portletName == null)
396: _portletName = servletConfig.getServletName();
397:
398: p = super .getInitParameter("expiration-cache");
399: if (p != null)
400: setExpirationCache(Integer.parseInt(p));
401:
402: p = super .getInitParameter("private");
403: if (p != null)
404: setPrivate(Boolean.valueOf(p).booleanValue());
405:
406: p = super .getInitParameter("buffer-size");
407: if (p != null)
408: setBufferSize(Integer.parseInt(p));
409:
410: p = super .getInitParameter("supported-locales");
411: if (p != null)
412: addSupportedLocales(p);
413:
414: p = super .getInitParameter("resource-bundle");
415: if (p != null)
416: setResourceBundle(p);
417:
418: p = super .getInitParameter("renderer-class");
419: if (p != null)
420: setRendererClass(p);
421:
422: _portletContext = new HttpPortletContext(getServletContext());
423:
424: try {
425: _portlet.init(this );
426: } catch (PortletException ex) {
427: throw new ServletException(ex);
428: }
429: }
430:
431: protected Object newInstance(Class targetClass, String className)
432: throws IllegalArgumentException {
433: Class cl = null;
434:
435: ClassLoader loader = Thread.currentThread()
436: .getContextClassLoader();
437:
438: try {
439: cl = Class.forName(className, false, loader);
440: } catch (ClassNotFoundException e) {
441: }
442:
443: if (cl == null)
444: throw new IllegalArgumentException("`" + className
445: + "' is not a known class");
446:
447: if (!targetClass.isAssignableFrom(cl))
448: throw new IllegalArgumentException("'" + className
449: + "' must implement " + targetClass.getName());
450:
451: if (Modifier.isAbstract(cl.getModifiers()))
452: throw new IllegalArgumentException("'" + className
453: + "' must not be abstract.");
454:
455: if (!Modifier.isPublic(cl.getModifiers()))
456: throw new IllegalArgumentException("'" + className
457: + "' must be public.");
458:
459: Constructor[] constructors = cl.getDeclaredConstructors();
460:
461: Constructor zeroArg = null;
462: for (int i = 0; i < constructors.length; i++) {
463: if (constructors[i].getParameterTypes().length == 0) {
464: zeroArg = constructors[i];
465: break;
466: }
467: }
468:
469: if (zeroArg == null
470: || !Modifier.isPublic(zeroArg.getModifiers()))
471: throw new IllegalArgumentException("'" + className
472: + "' must have a public zero arg constructor");
473:
474: Object obj = null;
475:
476: try {
477: obj = cl.newInstance();
478: } catch (Exception ex) {
479: throw new IllegalArgumentException("error instantiating `"
480: + className + "': " + ex.toString(), ex);
481: }
482:
483: return obj;
484: }
485:
486: public PortletConfig getPortletConfig() {
487: return this ;
488: }
489:
490: public String getInitParameter(String name) {
491: if (_initParamMap == null)
492: return super .getInitParameter(name);
493: else
494: return _initParamMap.get(name);
495: }
496:
497: public Enumeration getInitParameterNames() {
498: if (_initParamMap == null)
499: return super .getInitParameterNames();
500: else
501: return Collections.enumeration(_initParamMap.keySet());
502: }
503:
504: /**
505: * {@inheritDoc}
506: */
507: public int getExpirationCache() {
508: return _expirationCache;
509: }
510:
511: /**
512: * {@inheritDoc}
513: */
514: public boolean isPrivate() {
515: return _isPrivate;
516: }
517:
518: /**
519: * {@inheritDoc}
520: *
521: * This implementation returns true.
522: */
523: public boolean isWindowStateAllowed(PortletRequest request,
524: WindowState windowState) {
525: // XXX: todo: support a window-states init-param like
526: // normal, minimized
527: return true;
528: }
529:
530: /**
531: * {@inheritDoc}
532: *
533: * This implementation returns true.
534: */
535: public boolean isPortletModeAllowed(PortletRequest request,
536: PortletMode portletMode) {
537: // todo: see getSupportedContentTypes()
538: return true;
539: }
540:
541: /**
542: * {@inheritDoc}
543: *
544: * This implementation returns null, which means that all content
545: * types are supported.
546: */
547: public Set<String> getSupportedContentTypes(PortletMode portletMode) {
548: // XXX: todo: support a content-type init-param like
549: // edit(text/html), view(text/html, application/pdf)
550: return null;
551: }
552:
553: /**
554: * {@inheritDoc}
555: *
556: * This implementation returns null, which means that all locales are
557: * supported.
558: */
559: public Set<Locale> getSupportedLocales() {
560: return _supportedLocales;
561: }
562:
563: /**
564: * {@inheritDoc}
565: *
566: * This implementation returns null.
567: */
568: public PortletPreferences getDefaultPreferences() {
569: return _defaultPreferences;
570: }
571:
572: /**
573: * {@inheritDoc}
574: */
575: public ArrayList<PreferencesValidator> getPreferencesValidators() {
576: if (_defaultPreferences != null)
577: return _defaultPreferences.getPreferencesValidators();
578: else
579: return null;
580: }
581:
582: /**
583: * {@inheritDoc}
584: *
585: * This implementation returns null.
586: */
587: public Map<String, String> getRoleRefMap() {
588: return null;
589: // todo
590: }
591:
592: /**
593: * {@inheritDoc}
594: *
595: * This implementation returns null.
596: */
597: public ArrayList<Constraint> getConstraints() {
598: return null;
599: // todo
600: }
601:
602: /**
603: * {@inheritDoc}
604: */
605: public Renderer getRenderer() {
606: return _renderer;
607: }
608:
609: /**
610: * {@inheritDoc}
611: *
612: * This implementation returns PortletMode.VIEW.
613: */
614: public PortletMode handlePortletModeFailure(PortletRequest request,
615: PortletMode notAllowed) {
616: return PortletMode.VIEW;
617: }
618:
619: /**
620: * {@inheritDoc}
621: *
622: * This implementation returns WindowState.NORMAL.
623: */
624: public WindowState handleWindowStateFailure(PortletRequest request,
625: WindowState notAllowed) {
626: return WindowState.NORMAL;
627: }
628:
629: /**
630: * {@inheritDoc}
631: *
632: * This implementation does nothing.
633: */
634: public void handleConstraintFailure(RenderRequest request,
635: RenderResponse response, ConstraintFailureEvent event) {
636: }
637:
638: /**
639: * {@inheritDoc}
640: *
641: * This implementation does nothing.
642: */
643: public void handleException(RenderRequest request,
644: RenderResponse response, ExceptionEvent event) {
645: // XXX: todo: support error-page
646: }
647:
648: /**
649: * {@inheritDoc}
650: */
651: public String getPortletName() {
652: return _portletName;
653: }
654:
655: /**
656: * {@inheritDoc}
657: */
658: public PortletContext getPortletContext() {
659: return _portletContext;
660: }
661:
662: /**
663: * {@inheritDoc}
664: */
665: public ResourceBundle getResourceBundle(Locale locale) {
666: if (_resourceBundleFactory == null)
667: _resourceBundleFactory = new ResourceBundleFactory();
668:
669: return _resourceBundleFactory.getResourceBundle(locale);
670: }
671:
672: /**
673: * {@inheritDoc}
674: */
675: public int getBufferSize() {
676: return _bufferSize;
677: }
678:
679: protected void doGet(HttpServletRequest req, HttpServletResponse res)
680: throws ServletException, IOException {
681: doRequest(req, res);
682: }
683:
684: protected void doPost(HttpServletRequest req,
685: HttpServletResponse res) throws ServletException,
686: IOException {
687: doRequest(req, res);
688: }
689:
690: protected void doRequest(HttpServletRequest httpRequest,
691: HttpServletResponse httpResponse) throws ServletException,
692: IOException {
693: HttpPortletConnection connection = new HttpPortletConnection();
694: connection.start(_portal, _portletContext, httpRequest,
695: httpResponse, true);
696:
697: try {
698: Action action = connection.getAction(this , _namespace);
699:
700: if (action != null) {
701: try {
702: if (action.isTarget()) {
703: action.processAction(_portlet);
704: }
705: } finally {
706: action.finish();
707: }
708: }
709:
710: Render render = connection.getRender(this , _namespace);
711:
712: if (render != null) {
713: try {
714: render.render(_portlet);
715: } finally {
716: render.finish();
717: }
718: }
719:
720: connection.checkForFailure();
721: } catch (PortletException ex) {
722: throw new ServletException(ex);
723: } finally {
724: connection.finish();
725: }
726: }
727:
728: public void destroy() {
729: if (_portlet != null)
730: _portlet.destroy();
731: }
732:
733: }
|