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 * <%@ page language="java" %>
78 * <%@ page import="echopoint.ui.jsp.JspComponentPeer" %>
79 * <%@ page import="com.yourcompany.*" %>
80 * <%
81 * JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
82 * YourCustomComponent customC = (YourCustomComponent) peer.getComponent();
83 * Component child = customC.getTopBit();
84 * %>
85 * ...
86 * ...
87 * <span class="<%=peer.getStyle("topLineBit")%>" > Some styled text </span>
88 * <div class="<%=peer.getStyle("contentBit")%>" >
89 * <%
90 * peer.renderChild(out,child);
91 * %>
92 * </div>
93 * <image src="<%=peer.getStyle("contentBit")%>" >
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 * <%@ page language="java" %>
323 * <%@ page import="echopoint.ui.jsp.JspComponentPeer" %>
324 * <%
325 * JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
326 * %>
327 * ...
328 * ...
329 * <span class="<%=peer.getStyle("topLineBit")" %> Some styled text </span>
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 * <span <%=peer.classEquals("topLineBit")%> > Some styled text </span>
345 * </pre></blockquote>
346 * <p>
347 * instead of the slightly more terse :
348 * <p>
349 * <blockquote><pre>
350 * <span class="<%=peer.getStyle("topLineBit")" %> > Some styled text </span>
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 <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 * <%=peer.imgEquals("imageName1","vspace=5")%>
393 * </pre></blockquote>
394 * <p>
395 * instead of the slightly more terse :
396 * <p>
397 * <blockquote><pre>
398 * <img src="<%=peer.getImageUri("imageName1")%>" width="nnn" height="nnn" vspace=5 >
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 * <%@ page language="java" %>
446 * <%@ page import="echopoint.ui.jsp.JspComponentPeer" %>
447 * <%@ page import="com.yourcompany.*" %>
448 * <%
449 * JspComponentPeer peer = (JspComponentPeer) request.getAttribute(JspComponentPeer.JSPCOMPONENTPEER);
450 * YourCustomComponent customC = (YourCustomComponent) peer.getComponent();
451 * Component child = customC.getTopBit();
452 * %>
453 * ...
454 * ...
455 * <div>
456 * <%
457 * peer.renderChild(out,child);
458 * %>
459 * </div>
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 }