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 java.io.IOException;
053: import java.io.OutputStream;
054: import java.io.PrintWriter;
055: import java.util.Enumeration;
056: import java.util.LinkedHashSet;
057: import java.util.Locale;
058: import java.util.Map;
059: import java.util.ResourceBundle;
060: import java.util.Set;
061: import java.util.logging.Logger;
062:
063: /**
064: * <h3>Template</h3>
065: *
066: * To implement a renderer that adds decoration to content
067: * rendered with the PrintWriter, the following are overridden:
068: *
069: * <pre>
070: * protected void beginPage( PrintWriter out, RenderRequest request, String namespace )
071: * throws IOException
072: * {
073: * ...
074: * }
075: *
076: * protected void beginWindow( PrintWriter out, RenderRequest request, String namespace )
077: * throws IOException
078: * {
079: * ...
080: * }
081: *
082: * protected void endWindow( PrintWriter out, RenderRequest request, String namespace )
083: * throws IOException
084: * {
085: * ...
086: * }
087: *
088: * protected void endPage( PrintWriter out, RenderRequest request, String namespace )
089: * throws IOException
090: * {
091: * ...
092: * }
093: *
094: * <h3>Helper methods</h3>
095: * <ul>
096: * <li><code>{@link #printEscaped(java.io.PrintWriter,java.lang.CharSequence) printEscaped(out, string)};</code>
097: * <li><code>String contentType = {@link #getContentType(javax.portlet.RenderRequest) getContentType(request)}; </code>
098: * <li><code>Locale locale = {@link #getLocale(javax.portlet.RenderRequest) getLocale(request)}; </code>
099: * <li><code>String title = {@link #getTitle(javax.portlet.RenderRequest) getTitle(request)}; </code>
100: * <li><code>String shortTitle = {@link #getShortTitle(javax.portlet.RenderRequest) getShortTitle(request)}; </code>
101: * <li><code>PortletURL url = {@link #getControlURL(javax.portlet.RenderRequest) getControlURL(request)}; </code>
102: * <li><code>Set<PortletMode> portletModes = {@link #getPortletModes(javax.portlet.RenderRequest) getPortletModes(request)}; </code>
103: * <li><code>Set<WindowState> windowStates = {@link #getWindowStates(javax.portlet.RenderRequest) getWindowStates(request)}; </code>
104: * <li><code>ResourceBundle resourceBundle = {@link #getResourceBundle(javax.portlet.RenderRequest) getResourceBundle(request)}; </code>
105: * <li><code>PortletConfig portletConfig = {@link #getPortletConfig(javax.portlet.RenderRequest) getPortletConfig(request)}; </code>
106: * <li><code>RenderResponse renderResponse = {@link #getRenderResponse(javax.portlet.RenderRequest) getRenderResponse(request)}; </code>
107: */
108:
109: abstract public class AbstractRenderer implements Renderer {
110: public static final String PAGE_HEADER_RENDERED = "com.caucho.portal.generic.AbstractRenderer.PAGE_HEADER_RENDERED";
111:
112: protected static final Logger log = Logger
113: .getLogger(AbstractRenderer.class.getName());
114:
115: private boolean _isAlwaysWrite = true;
116: private boolean _isAlwaysStream;
117: private String _defaultContentType = "text/html";
118: private int _bufferSize = -1;
119:
120: private Boolean _isDecorateWindow;
121:
122: /**
123: * If true beginWindow() and endWindow() are always called, if false never
124: * called, the default is to call beginWindow() and endWindow() unless
125: * beginPage() and endPage() are called.
126: */
127: public void setDecorateWindow(boolean isDecorateWindow) {
128: _isDecorateWindow = isDecorateWindow ? Boolean.TRUE
129: : Boolean.FALSE;
130: }
131:
132: /**
133: * If true the portal will always call getWriter(), even if the portlet does
134: * not call getWriter(), unless getOutputStream() has been called.
135: *
136: * The default is true.
137: */
138: public void setAlwaysWrite(boolean isAlwaysWrite) {
139: _isAlwaysWrite = isAlwaysWrite;
140: }
141:
142: /**
143: * If true the portal will always call getOutputStream(), even if the portlet
144: * does not call getOutputStream(), unless getWriter() has been called.
145: *
146: * The default is false.
147: */
148: public void setAlwaysStream(boolean isAlwaysStream) {
149: _isAlwaysStream = isAlwaysStream;
150: }
151:
152: /**
153: * The default content type used if {@link #isAlwaysWrite()}
154: * or {@link #isAlwaysStream()} is true and a
155: * Writer or OutputStream is obtained before the content type of the
156: * response has been set, default is "text/html".
157: */
158: public void setDefaultContentType(String defaultContentType) {
159: _defaultContentType = defaultContentType;
160: }
161:
162: /**
163: * The default is -1, which allows the portal to choose a buffer size.
164: * 0 disables buffering of the renderer output.
165: */
166: public void setBufferSize(int bufferSize) {
167: _bufferSize = bufferSize;
168: }
169:
170: /**
171: * Return true if the WindowState is allowed.
172: *
173: * <code>portletRequest.getResponseContentType()</code> can be used
174: * if the allowed window states is dependent on the mime type of the
175: * response.
176: *
177: * The default is to return true.
178: */
179: public boolean isWindowStateAllowed(PortletRequest request,
180: WindowState windowState) {
181: return true;
182: }
183:
184: /**
185: * Return true if the WindowState is allowed.
186: *
187: * <code>portletRequest.getResponseContentType()</code> can be used
188: * if the allowed portlet modes is dependent on the mime type of the
189: * response.
190: *
191: * The default is to return true.
192: */
193: public boolean isPortletModeAllowed(PortletRequest request,
194: PortletMode portletMode) {
195: return true;
196: }
197:
198: public boolean isAlwaysWrite() {
199: return _isAlwaysWrite;
200: }
201:
202: public boolean isAlwaysStream() {
203: return _isAlwaysStream;
204: }
205:
206: public String getDefaultContentType() {
207: return _defaultContentType;
208: }
209:
210: public int getBufferSize() {
211: return _bufferSize;
212: }
213:
214: /**
215: * Return a Writer that wraps the passed PrintWriter, or null
216: * if there is no specialized writer for this request.
217: *
218: * This implementation calls beginPage() if needed and beginWindow()
219: * before returning the writer.
220: */
221: public PrintWriter getWriter(PrintWriter out,
222: RenderRequest request, String namespace) throws IOException {
223: boolean doPage = (request.getAttribute(PAGE_HEADER_RENDERED) == null);
224:
225: boolean doWindow = _isDecorateWindow == null ? !doPage
226: : _isDecorateWindow.booleanValue();
227:
228: if (doPage) {
229: beginPage(out, request, namespace);
230: request.setAttribute(PAGE_HEADER_RENDERED, this );
231: }
232:
233: if (doWindow)
234: beginWindow(out, request, namespace);
235:
236: return out;
237: }
238:
239: /**
240: * Derived classes override to insert header content for a page,
241: * called only when beginPage() has not been called on some instance of
242: * AbstractRenderer for this request.
243: */
244: protected void beginPage(PrintWriter out, RenderRequest request,
245: String namespace) throws IOException {
246: }
247:
248: /**
249: * Derived classes override to insert header content before a portlet
250: * has been rendered.
251: */
252: protected void beginWindow(PrintWriter out, RenderRequest request,
253: String namespace) throws IOException {
254: }
255:
256: /**
257: * Derived classes override to append footer content after a portlet
258: * has been rendered.
259: */
260: protected void endWindow(PrintWriter out, RenderRequest request,
261: String namespace) throws IOException {
262: }
263:
264: /**
265: * Derived classes override to insert footer content for a page,
266: * called from only when beginPage() has been called for the Window.
267: */
268: protected void endPage(PrintWriter out, RenderRequest request,
269: String namespace) throws IOException {
270: }
271:
272: /**
273: * Finish with a Writer produced by this factory.
274: * This may be called even if the writer threw an Exception.
275: *
276: * This implementation calls endWindow() and endPage() if needed.
277: */
278: public void finish(PrintWriter out, RenderRequest request,
279: String namespace, boolean isDiscarded) throws IOException {
280: boolean doPage = (this == request
281: .getAttribute(PAGE_HEADER_RENDERED));
282:
283: boolean doWindow = _isDecorateWindow == null ? !doPage
284: : _isDecorateWindow.booleanValue();
285:
286: if (!isDiscarded) {
287: if (doWindow)
288: endWindow(out, request, namespace);
289:
290: if (doPage)
291: endPage(out, request, namespace);
292: } else {
293: if (this == request.getAttribute(PAGE_HEADER_RENDERED))
294: request.removeAttribute(PAGE_HEADER_RENDERED);
295: }
296: }
297:
298: /**
299: * Return an OutputStream that wraps the passed OutputStream, or null
300: * if there is no specialized writer for this request.
301: */
302: public OutputStream getOutputStream(OutputStream out,
303: RenderRequest request, String namespace) throws IOException {
304: boolean doPage = (request.getAttribute(PAGE_HEADER_RENDERED) == null);
305:
306: boolean doWindow = _isDecorateWindow == null ? !doPage
307: : _isDecorateWindow.booleanValue();
308:
309: if (doPage) {
310: beginPage(out, request, namespace);
311: request.setAttribute(PAGE_HEADER_RENDERED, this );
312: }
313:
314: if (doWindow)
315: beginWindow(out, request, namespace);
316:
317: return out;
318: }
319:
320: /**
321: * Derived classes override to insert header content for a page,
322: * called only when beginPage() has not been called on some instance of
323: * AbstractRenderer for this request.
324: *
325: * <code>request.getResponseContentType()</code> can be used
326: * to determine the content type.
327: *
328: * request.getAttribute("javax.portlet.title") may contain a title
329: * for the Window, if the portlet has set one.
330: */
331: protected void beginPage(OutputStream out, RenderRequest request,
332: String namespace) throws IOException {
333: }
334:
335: /**
336: * Derived classes override to insert header content before a portlet
337: * has been rendered.
338: *
339: * <code>request.getResponseContentType()</code> is used
340: * to determine the content type.
341: *
342: * request.getAttribute("javax.portlet.title") may contain a title
343: * for the Window, if the portlet has set one.
344: */
345: protected void beginWindow(OutputStream out, RenderRequest request,
346: String namespace) throws IOException {
347: }
348:
349: /**
350: * Derived classes override to append footer content after a portlet
351: * has been rendered.
352: *
353: * <code>request.getResponseContentType()</code> can be used
354: * to determine the content type.
355: *
356: */
357: protected void endWindow(OutputStream out, RenderRequest request,
358: String namespace) throws IOException {
359: }
360:
361: /**
362: * Derived classes override to insert footer content for a page,
363: * called from only when beginPage() has been called for the Window.
364: *
365: * <code>request.getResponseContentType()</code> can be used
366: * to determine the content type.
367: */
368: protected void endPage(OutputStream out, RenderRequest request,
369: String namespace) throws IOException {
370: }
371:
372: /**
373: * Finish with an OutputStream produced by this factory.
374: * This may be called even if the outputStream threw an Exception.
375: *
376: * This implementation calls endWindow() and endPage() if needed.
377: */
378: public void finish(OutputStream out, RenderRequest request,
379: String namespace, boolean isDiscarded) throws IOException {
380: boolean doPage = (this == request
381: .getAttribute(PAGE_HEADER_RENDERED));
382:
383: boolean doWindow = _isDecorateWindow == null ? !doPage
384: : _isDecorateWindow.booleanValue();
385:
386: if (!isDiscarded) {
387: if (doWindow)
388: endWindow(out, request, namespace);
389:
390: if (doPage)
391: endPage(out, request, namespace);
392: } else {
393: if (this == request.getAttribute(PAGE_HEADER_RENDERED))
394: request.removeAttribute(PAGE_HEADER_RENDERED);
395: }
396: }
397:
398: /**
399: * Return the content type to use for the response.
400: */
401: protected String getContentType(RenderRequest request) {
402: return request.getResponseContentType();
403: }
404:
405: /**
406: * Return the locale to use for the response.
407: */
408: protected Locale getLocale(RenderRequest request) {
409: return request.getLocale();
410: }
411:
412: /**
413: * Return the title for the window, or null if it has not been set.
414: * The title is the first of:
415: * <ul>
416: * <li>A title set by the portlet using response.setTitle()
417: * or request.setAttribute("javax.portlet.title");
418: * <li>The value of a lookup in the windows resource bundle for
419: * "javax.portlet.title" using the locale dertmined by
420: * {@link #getLocale(javax.portlet.RenderRequest) getLocale(request)}.
421: * </ul>
422: */
423: protected String getTitle(RenderRequest request) {
424: String title = (String) request
425: .getAttribute("javax.portlet.title");
426:
427: if (title == null) {
428: PortletConfig portletConfig = (PortletConfig) request
429: .getAttribute("javax.portlet.portletConfig");
430:
431: Locale locale = getLocale(request);
432: ResourceBundle bundle = portletConfig
433: .getResourceBundle(locale);
434:
435: if (bundle != null)
436: title = bundle.getString("javax.portlet.title");
437: }
438:
439: return title;
440: }
441:
442: /**
443: * Return the short title for the window, or null if a title is not
444: * available.
445: *
446: * <ul>
447: * <li>A title set by the portlet using
448: * request.setAttribute("javax.portlet.short-title");
449: * <li>A title set by the portlet using response.setTitle()
450: * or request.setAttribute("javax.portlet.title");
451: * <li>The value of a lookup in the windows resource bundle for
452: * "javax.portlet.short-title" using the locale determined by
453: * {@link #getLocale(javax.portlet.RenderRequest) getLocale(request)}.
454: * <li>The value of a lookup in the windows resource bundle for
455: * "javax.portlet.title" using the locale determined by
456: * {@link #getLocale(javax.portlet.RenderRequest) getLocale(request)}.
457: * </ul>
458: */
459: protected String getShortTitle(RenderRequest request) {
460: String title = (String) request
461: .getAttribute("javax.portlet.short-title");
462:
463: if (title == null)
464: title = (String) request
465: .getAttribute("javax.portlet.title");
466:
467: if (title == null) {
468: PortletConfig portletConfig = (PortletConfig) request
469: .getAttribute("javax.portlet.portletConfig");
470:
471: Locale locale = getLocale(request);
472: ResourceBundle bundle = portletConfig
473: .getResourceBundle(locale);
474:
475: title = bundle.getString("javax.portlet.short-title");
476:
477: if (title == null)
478: title = bundle.getString("javax.portlet.title");
479: }
480:
481: return title;
482: }
483:
484: /**
485: * Create a PortletURL for a control, such as a link to change the window
486: * state or portlet mode. Control URLs are render urls that maintain
487: * existing render parameters.
488: */
489: protected PortletURL createControlURL(RenderRequest request) {
490: RenderResponse response = (RenderResponse) request
491: .getAttribute("javax.portlet.renderResponse");
492:
493: Map<String, String[]> renderParamMap = request
494: .getParameterMap();
495:
496: PortletURL url = response.createRenderURL();
497:
498: url.setParameters(renderParamMap);
499:
500: return url;
501: }
502:
503: /**
504: * Return an Set of the possible portlet modes that the window can
505: * have. The list of all possibilities is first obtained from
506: * {@link javax.portlet.PortalContext#getSupportedPortletModesa() PortalContext.getSupportedPortletModes()}.
507: * Each one is checked with
508: * {@link javax.portlet.PortletRequest#isPortletModeAllowed(javax.portlet.PortletMode) request.isPortletModeAllowed()},
509: * if it is allowed then it is included in the returned Set.
510: */
511: protected Set<PortletMode> getPortletModes(RenderRequest request) {
512: Set<PortletMode> portletModes = new LinkedHashSet<PortletMode>();
513:
514: Enumeration<PortletMode> e = (Enumeration<PortletMode>) request
515: .getPortalContext().getSupportedPortletModes();
516:
517: while (e.hasMoreElements()) {
518: PortletMode portletMode = e.nextElement();
519:
520: if (request.isPortletModeAllowed(portletMode))
521: portletModes.add(portletMode);
522: }
523:
524: return portletModes;
525: }
526:
527: /**
528: * Return an Set of the possible window states that the window can
529: * have. The list of all possibilities is first obtained from
530: * {@link javax.portlet.PortalContext#getSupportedWindowStates() PortalContext.getSupportedWindowStates()}.
531: * Each one is checked with
532: * {@link javax.portlet.PortletRequest#isWindowStateAllowed(javax.portlet.WindowState) request.isWindowStateAllowed()},
533: * if it is allowed then it is included in the returned Set.
534: */
535: protected Set<WindowState> getWindowStates(RenderRequest request) {
536: Set<WindowState> windowStates = new LinkedHashSet<WindowState>();
537:
538: Enumeration<WindowState> e = (Enumeration<WindowState>) request
539: .getPortalContext().getSupportedWindowStates();
540:
541: while (e.hasMoreElements()) {
542: WindowState windowState = e.nextElement();
543:
544: if (request.isWindowStateAllowed(windowState))
545: windowStates.add(windowState);
546: }
547:
548: return windowStates;
549: }
550:
551: /**
552: * Get a resource bundle for the portlet being rendered.
553: */
554: protected ResourceBundle getResourceBundle(RenderRequest request) {
555: Locale locale = getLocale(request);
556: PortletConfig portletConfig = getPortletConfig(request);
557: return portletConfig.getResourceBundle(locale);
558: }
559:
560: /**
561: * Get the PortletConfig for the portlet being rendered.
562: */
563: protected PortletConfig getPortletConfig(RenderRequest request) {
564: return (PortletConfig) request
565: .getAttribute("javax.portlet.portletConfig");
566: }
567:
568: /**
569: * Get the RenderResponse for the portlet being rendered.
570: */
571: protected RenderResponse getRenderResponse(RenderRequest request) {
572: return (RenderResponse) request
573: .getAttribute("javax.portlet.renderResponse");
574: }
575:
576: /**
577: * Print a string with appropriate escaping for XML, for example '<'
578: * becomes '&lt;'.
579: */
580: protected PrintWriter printEscaped(PrintWriter out,
581: CharSequence string) {
582: if (string == null) {
583: out.print((String) null);
584: return out;
585: }
586:
587: for (int i = 0; i < string.length(); i++) {
588: char ch = string.charAt(i);
589:
590: switch (ch) {
591: case '<':
592: out.print("<");
593: break;
594: case '>':
595: out.print(">");
596: break;
597: case '&':
598: out.print("&");
599: break;
600: case '\"':
601: out.print(""");
602: break;
603: case '\'':
604: out.print("’");
605: break;
606: default:
607: out.print(ch);
608: }
609: }
610:
611: return out;
612: }
613:
614: }
|