Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: echopoint/ui/jsp/JspComponentPeer.java


1   package echopoint.ui.jsp;
2   /* 
3    * This file is part of the Echo Point Project.  This project is a collection
4    * of Components that have extended the Echo Web Application Framework.
5    *
6    * EchoPoint is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as published by
8    * the Free Software Foundation; either version 2 of the License, or
9    * (at your option) any later version.
10   *
11   * EchoPoint is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with Echo Point; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.HashMap;
26  import java.util.Map;
27  
28  import javax.servlet.Servlet;
29  import javax.servlet.ServletException;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpSession;
33  import javax.servlet.jsp.JspFactory;
34  import javax.servlet.jsp.JspWriter;
35  import javax.servlet.jsp.PageContext;
36  
37  import nextapp.echo.Component;
38  import nextapp.echo.ImageReference;
39  import nextapp.echo.event.ImageUpdateListener;
40  import nextapp.echoservlet.ComponentPeer;
41  import nextapp.echoservlet.ComponentStyle;
42  import nextapp.echoservlet.RenderingContext;
43  import nextapp.echoservlet.html.Element;
44  import nextapp.echoservlet.html.Renderable;
45  
46  import echopoint.ui.util.EchoPointComponentPeer;
47  import echopoint.ui.util.NoNameElement;
48  import echopoint.util.throwable.ThrowableKit;
49  
50  /**
51   * <code>JspComponentPeer</code> is an abstract component peer class 
52   * that allows JSP page fragments to be used to render a component.
53   * <p>
54   * This class is useful if you prefer updating JSP/HTML markup rather 
55   * than writing Java nextapp.echoservlet.html.Element statements.
56   * <p>
57   * To use this class you need to implement the method <code>getJspPath()</code>.
58   * as this nominates the JSP path to include as peer content.  You might 
59   * want to use different JSP pages depending on the state of the 
60   * underlying component.  
61   * <p>
62   * You will very likely want to overide <code>preRender()</code>
63   * as well.  This method allows any <code>ComponentStyle</code>'s and 
64   * scripts to be added to the Echo HTML document.  You might also
65   * want to add beans to the HttpSession or HttpServletRequest
66   * objects at this time.
67   * <p>  
68   * The results of these pre-render methods are then available inside 
69   * the JSP page because <code>JspComponentPeer</code> adds itself as 
70   * a request scope attribute called "echopoint.ui.jsp.JspComponentPeer" 
71   * or in Java terms <code>JspComponentPeer.JSPCOMPONENTPEER</code>.
72   * <p>
73   * You can then access the peer in the JSP page as follows :
74   * <p>
75   * <blockquote><pre>
76   *      ...
77   *      &lt;%@ page language="java" %&gt;
78   *      &lt;%@ page import="echopoint.ui.jsp.JspComponentPeer" %&gt;
79   *      &lt;%@ page import="com.yourcompany.*" %&gt;
80   *      &lt;%
81   *      JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
82   *      YourCustomComponent customC = (YourCustomComponent) peer.getComponent();
83   *      Component child = customC.getTopBit();
84   *      %&gt;
85   *      ...
86   *      ...
87   *      &lt;span class="&lt;%=peer.getStyle("topLineBit")%&gt;" &gt; Some styled text &lt;/span&gt;
88   *      &lt;div class="&lt;%=peer.getStyle("contentBit")%&gt;" &gt;
89   *      &lt;%
90   *         peer.renderChild(out,child);  
91   *      %&gt;
92   *      &lt;/div&gt;
93   *      &lt;image src="&lt;%=peer.getStyle("contentBit")%&gt;" &gt;
94   * </pre></blockquote>
95   * <p>
96   * You will also have to associated your custom peer with its component
97   * via a method such as :
98   * <p>
99   * <blockquote><pre>
100  * public static void register() {
101  *       nextapp.echoservlet.EchoServer.loadPeerBindings("mycompany.ui.MyCustomComponent");
102  * }
103  * </pre></blockquote>
104  * <p>
105  * This class differs from the other JSP integration class JspTemplatePanel in a number of 
106  * ways.  Firstly it only evaluates the JSP once, at the time the Echo servlet is writing 
107  * to the servlet stream.  Secondly it does not allow "styles" to be dynamically
108  * evaluated like in a <code>JspTemplatePanel</code>.  And finally while it allows
109  * child components to be embedded in the output, they are always pre-rendered and are
110  * not "named" via the special JSP tags.
111  * 
112  */
113 public abstract class JspComponentPeer extends EchoPointComponentPeer implements Renderable, ImageUpdateListener {
114 
115   /**
116    * This string is used as the request scope attribute name which holds this
117    * JspComponentPeer object.
118    */
119   public static final String JSPCOMPONENTPEER = "echopoint.ui.jsp.JspComponentPeer";
120 
121   private RenderingContext rc;  
122   private PageContext pageContext;
123   private JspFactory factory;
124   private Servlet servlet;
125   private HttpServletRequest request;
126   private HttpServletResponse response;
127   private HttpSession session;
128   
129   private Map childElementMap;
130   private Map styleMap;
131   private String jspPath;
132   private boolean isLoudErrorsUsed = true;
133   
134   /**
135    * The render method has been made final.  This method now sets up the
136    * objects needed during the actual JSP rendering, such
137    * as the JSP page to include, any scripts or component style 
138    * names required and will then pre-render any child components.
139    * <p>
140    * The order is as follows :
141    * <ol>
142    * <li>getJspPath() is called</li> 
143    * <li>preRender() is called</li> 
144    * <li>any child peers are pre-rendered</li> 
145    * </ol> 
146    *  
147    * @see nextapp.echoservlet.ComponentPeer#render(nextapp.echoservlet.RenderingContext, nextapp.echoservlet.html.Element)
148    */
149   public final void render(RenderingContext rc, Element parent) {
150     this.rc = rc;
151     
152     servlet = rc.getConnection().getServer();
153     request = rc.getConnection().getRequest();
154     response = rc.getConnection().getResponse();
155     session = request.getSession();
156     
157     factory = JspFactory.getDefaultFactory();
158     if (factory == null)
159       throw new IllegalStateException("The default JSPFactory could not be obtained");
160       
161     pageContext = factory.getPageContext(servlet, request, response, null, true, JspWriter.NO_BUFFER, true);
162     if (pageContext == null)
163       throw new IllegalStateException("The JSPFactory could provide a page context for this servlet");
164 
165     //
166     // ask them what JSP page to use given the current state
167     jspPath = getJspPath();
168     if (jspPath == null || jspPath.length() == 0)
169       throw new IllegalStateException("getJspPath must return a non empty JSP path");
170     
171     //
172     // now call the abstract method to setup any styles and
173     // scripts etc...    
174     preRender(rc);
175     
176     //
177     // if we have any children then render them and save them 
178     // into the child map;
179     ComponentPeer[] children = getChildren();
180     if (children.length > 0) {
181       childElementMap = new HashMap();
182       for (int i = 0; i < children.length; i++) {
183         NoNameElement noNameE = new NoNameElement();
184         children[i].render(rc,noNameE);
185         childElementMap.put(children[i].getComponent(),noNameE);
186       } 
187     }
188     
189     //
190     // add ourselves as a Renderable!  This will allow us to be
191     // called to output JSp content during the Echo
192     // servlet output.
193     parent.add(this);
194   }
195   
196   /**
197    * This method is called during Echo servlet output and it includes
198    * the actual JSP content that has been setup for this 
199    * component peer via getJspPath().
200    * 
201    * @see nextapp.echoservlet.html.Renderable#render(java.io.PrintWriter, int, boolean)
202    */
203   public final void render(PrintWriter pw, int depth, boolean parentWhitespaceRelevant) {
204     
205     try {
206       //
207       // Put ourselves in as a request attribute 
208       request.setAttribute(JSPCOMPONENTPEER,this);
209       ///////////////////////////////////////////////
210       // include the page
211       ///////////////////////////////////////////////
212       pageContext.include(jspPath);
213       factory.releasePageContext(pageContext);
214       pageContext = null;
215     } catch (ServletException e) {
216       reportError(pw,e);
217     } catch (IOException e) {
218       throw ThrowableKit.makeRuntimeException(e);
219     } finally {
220       //
221       //remove everything just added to request scope 
222       //to avoid namespace pollution.
223       //
224       request.removeAttribute(JSPCOMPONENTPEER);
225       // cleanup the factory and page context
226       if (factory != null && pageContext != null)
227         factory.releasePageContext(pageContext);
228       factory = null;  
229       servlet = null;
230       request = null;
231       response = null;
232       rc = null;
233       childElementMap = null;
234       styleMap = null;
235       jspPath = null;
236     }
237   }
238   
239   /**
240    * Report a ServletError, in all likely hood an JSP compile 
241    * error, by make it more pretty.
242    */
243   private void reportError(PrintWriter pw, ServletException e) {
244     String temp = e.toString();
245     StringBuffer sb = new StringBuffer();
246     for (int i = 0; i < temp.length(); i++) {
247       char c = temp.charAt(i);
248       if (c == '\n')
249         sb.append("<br>");
250       else
251         sb.append(c);  
252     }
253     String errorMsg = preNewLines(e.toString());
254     
255     StringWriter swStackTrace = new StringWriter();
256     PrintWriter pwStackTrace = new PrintWriter(swStackTrace);
257     e.printStackTrace(pwStackTrace);
258     String stackTraceMsg = preNewLines(swStackTrace.toString());
259     
260     
261     if (isLoudErrorsUsed) {
262       pw.println("<div style=\"background:#acbcdc;color:white;font-size:12;border-width:1; border-style:solid; border-color:black; padding:4; margin:4\" >");
263       pw.println("<blink><b>");
264       pw.println("<p>JSP Component Peer Exception : '<blockquote><pre>" + errorMsg + "</pre></blockquote>'");
265       pw.println("<p>Exception Stack Trace : '<blockquote><pre>" + stackTraceMsg + "</pre></blockquote>'");
266       pw.println("</b></blink></div>");
267     } else {
268       pw.println("<!-- ");
269       pw.println("JSP Component Peer Exception : '" + errorMsg + "'");
270       pw.println("Exception Stack Trace : '" + stackTraceMsg + "'");
271       pw.println(" -->");
272     }
273   }
274     
275   private String preNewLines(String s) {
276     StringBuffer sb = new StringBuffer(s.length());
277     for (int i = 0; i < s.length(); i++) {
278       char c = s.charAt(i);
279       if (c == '\n') {
280         sb.append("<br>");
281         while (i < s.length()-1 && s.charAt(i+1) == '\n')
282           i++;  
283       } else {
284         sb.append(c);  
285       }
286     }
287     return sb.toString();
288   }
289 
290   /**
291    * This method is an easy way to add completed ComponentStyle
292    * objects to the document.  The Echo generated name is returned.
293    * <p>
294    * These component style names can then be used in class="xxx" 
295    * statements inside the JSP markup.
296    * <p>
297    * A style keyed as "default" is initially primed and is
298    * equivalent to <code>ComponentStyle.forComponent(this);</code>
299    *  
300    * @param styleKey - the key name to be later used with getStyle()
301    * @param componentStyle - the completed ComponentStyle object.
302    * @return - the Echo generated style name.
303    */
304   protected String putStyle(String styleKey, ComponentStyle componentStyle) {
305     if (styleMap == null)
306       styleMap = new HashMap();
307     
308     String styleName = rc.getDocument().addStyle(componentStyle);
309     styleMap.put(styleKey,styleName);
310     return styleName;
311   }
312 
313   /**
314    * Returns the Echo style name that was previously stored away
315    * using the <code>putStyle()<code> method.  This is very useful
316    * in HTML class="...." statements inside the JSP.
317    * <p>
318    * Usage is demonstrated in the following JSP fragment :
319    * <p>
320    * <blockquote><pre>
321    *      ...
322    *      &lt;%@ page language="java" %&gt;
323    *      &lt;%@ page import="echopoint.ui.jsp.JspComponentPeer" %&gt;
324    *      &lt;%
325    *      JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
326    *      %&gt;
327    *      ...
328    *      ...
329    *      &lt;span class="&lt;%=peer.getStyle("topLineBit")" %&gt; Some styled text &lt;/span&gt;
330    * </pre></blockquote>
331    *  
332    * @param styleKey - the styleKey used when storing the style
333    * @return the style name
334    */
335   public String getStyle(String styleKey) {
336     return styleMap == null ? null : (String) styleMap.get(styleKey);
337   }
338   
339   /**
340    * This is a synonym for the <code>getStyle()</code> method but it
341    * adds the text class="xxx" for you.  It allows you to write
342    * <p>
343    * <blockquote><pre>
344    *      &lt;span &lt;%=peer.classEquals("topLineBit")%&gt; &gt; Some styled text &lt;/span&gt;
345    * </pre></blockquote>
346    * <p>
347    * instead of the slightly more terse :
348    * <p>
349    * <blockquote><pre>
350    *      &lt;span class="&lt;%=peer.getStyle("topLineBit")" %&gt; &gt; Some styled text &lt;/span&gt;
351    * </pre></blockquote>
352    * 
353    *  
354    * @param styleKey
355    * @return
356    */
357   public String classEquals(String styleKey) {
358     StringBuffer sb = new StringBuffer(" class=\"");
359     sb.append(getStyle(styleKey));
360     sb.append("\" ");
361     return sb.toString();
362   }
363 
364   /**
365    * This will return the URI for a managed image or null if it
366    * can not be found.
367    *  
368    * @param imageName - the name of a managed image
369    * @return the URI for the image or null if it can not be found.
370    */
371   public String getImageUri(String imageName) {
372     return getImageUri(rc,imageName);
373   }
374 
375   /**
376    * Synonym for imgEquals(imageName,"");
377    * 
378    * @see JspComponentPeer#imgEquals(String, String)
379    */
380   public String imgEquals(String imageName) {
381     return imgEquals(imageName,"");
382   }
383 
384   /**
385    * This is a synonym for the <code>getImageUri()</code> method but it
386    * adds the text &lt;img src="xxx" width="nnn" height="nnn" ...> plus
387    * any extra attribuets that have been provided on the call. 
388    * <p>
389    * It allows you to write
390    * <p>
391    * <blockquote><pre>
392    *      &lt;%=peer.imgEquals("imageName1","vspace=5")%&gt;
393    * </pre></blockquote>
394    * <p>
395    * instead of the slightly more terse :
396    * <p>
397    * <blockquote><pre>
398    *      &lt;img src="&lt;%=peer.getImageUri("imageName1")%&gt;" width="nnn" height="nnn" vspace=5 &gt;
399    * </pre></blockquote>
400    * 
401    * @param styleKey
402    * @return
403    */
404   public String imgEquals(String imageName, String extraAttributes) {
405     String imageURI = getImageUri(imageName);
406 
407     StringBuffer sb = new StringBuffer("<img ");
408     sb.append(" src=\"");
409     sb.append(imageURI);
410     sb.append("\" ");
411     if (imageURI != null) {
412       ImageReference imageRef = getImage(imageName);
413       if (imageRef.getWidth() > 0) {
414         sb.append(" width=\"");
415         sb.append(imageRef.getWidth());
416         sb.append("\" ");   
417       }
418       if (imageRef.getHeight() > 0) {
419         sb.append(" height=\"");
420         sb.append(imageRef.getHeight());
421         sb.append("\" ");   
422       }
423     }
424     if (extraAttributes != null && extraAttributes.length() > 0) {
425       sb.append(extraAttributes);
426     }
427     sb.append(">");
428     return sb.toString();
429   }
430   
431   /**
432    * This method is available so that the JSP can render a child 
433    * of the main component.  They key to use is the actual 
434    * child component itself.  
435    * <p>
436    * Child components of the peers main component are pre-rendered
437    * during the intital setup, before the JSP page is rendered.  You
438    * can use this method to access the pre-rendered child components
439    * inside the JSP markup itself.
440    * <p>
441    * Usage is demonstrated in the following JSP fragment :
442    * <p>
443    * <blockquote><pre>
444    *      ...
445    *      &lt;%@ page language="java" %&gt;
446    *      &lt;%@ page import="echopoint.ui.jsp.JspComponentPeer" %&gt;
447    *      &lt;%@ page import="com.yourcompany.*" %&gt;
448    *      &lt;%
449    *      JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
450    *      YourCustomComponent customC = (YourCustomComponent) peer.getComponent();
451    *      Component child = customC.getTopBit();
452    *      %&gt;
453    *      ...
454    *      ...
455    *      &lt;div&gt;
456    *      &lt;%
457    *         peer.renderChild(out,child);  
458    *      %&gt;
459    *      &lt;/div&gt;
460    * </pre></blockquote>
461    *  
462    * @param out - the JSP output writer
463    * @param childComponent - the component in question
464    */
465   public void renderChild(Writer out, Component childComponent) {
466     // if we have no map we have no child peers
467     if (childElementMap == null)
468       return;
469     Element e = (Element) childElementMap.get(childComponent);
470     if (e != null) {
471       PrintWriter pw = new PrintWriter(out);
472       e.render(pw,0,true);
473     }
474   }
475 
476   /** 
477    * Returns the current RenderingContext associated with the peer or null if none is available.
478    * 
479    * @return the current RenderingContext associated with the peer or null if none is available.
480    */
481   public RenderingContext getRenderingContext() {
482     return rc;
483   }
484 
485   /** 
486    * Returns the JSPFactory currently in use or null if none is available.
487    * @return the JSPFactory currently in use or null if none is available.
488    */
489   public JspFactory getFactory() {
490     return factory;
491   }
492 
493   /** 
494    * Returns the PageContext currently in use or null if none is available.
495    * @return the PageContext currently in use or null if none is available.
496    */
497   public PageContext getPageContext() {
498     return pageContext;
499   }
500 
501   /** 
502    * Returns the HttpServletRequest currently in use or null if none is available.
503    * @return the HttpServletRequest currently in use or null if none is available.
504    */
505   public HttpServletRequest getRequest() {
506     return request;
507   }
508 
509   /** 
510    * Returns the HttpServletResponse currently in use or null if none is available.
511    * @return the HttpServletResponse currently in use or null if none is available.
512    */
513   public HttpServletResponse getResponse() {
514     return response;
515   }
516 
517   /** 
518    * Returns the Servlet currently in use or null if none is available.
519    * @return the Servlet currently in use or null if none is available.
520    */
521   public Servlet getServlet() {
522     return servlet;
523   }
524 
525   /** 
526    * Returns the HttpSession currently in use or null if none is available.
527    * @return the HttpSession currently in use or null if none is available.
528    */
529   public HttpSession getSession() {
530     return session;
531   }
532   
533   /** 
534    * Returns true if loud error message generation is used.
535    * @return true if loud error message generation is used.
536    */
537   public boolean isLoudErrorsUsed() {
538     return isLoudErrorsUsed;
539   }
540 
541   /** 
542    * If this is set to true, then a loud, obvious HTML error
543    * will be output if some catatrophic error occurs
544    * during JSP rendering.  Otherwise a quite HTML
545    * comment will be emitted.
546    * 
547    * @param isLoudErrorsUsed - true if loud errors are to be used
548    */
549   public void setLoudErrorsUsed(boolean isLoudErrorsUsed) {
550     this.isLoudErrorsUsed = isLoudErrorsUsed;
551   }
552 
553 
554   
555   /** 
556    * This abstract method is required to return the path of the JSP page 
557    * that will be included as the content of this component peer.  
558    * It must be non null and non-empty, otherwise an
559    * IllegalStateException is thrown.
560    * 
561    * @return the path of the JSP to be rendered as this peer.
562    * 
563    * @throws IllegalStateException if the path is null or empty
564    */
565   protected abstract String getJspPath();
566 
567   /**
568    * This is called to setup any objects needed before
569    * the actual JSP page is invoked.  This could include
570    * things such as <code>ComponentStyle</code> objects and any
571    * special scripts and services required.
572    * <p>
573    * You might also want to add beans to the HttpSession or 
574    * HttpServletRequest objects at this time.  
575    * <p>
576    * These scripts etc.. can be added to the head of the HTML document because 
577    * this method is called before actual JSP rendering occurs.
578    * <p>
579    * This implementation of the method adds a ComponentStyle
580    * mapped as "default", via is built via 
581    * <code>ComponentStyle.forComponent(this);</code>
582    *  
583    * @param rc - the RenderingContext
584    */
585   protected void preRender(RenderingContext rc) {
586     //
587     // we always create a default style
588     ComponentStyle style = ComponentStyle.forComponent(this);
589     style.addElementType("");
590     putStyle("default",style);
591 
592   }
593 
594 }