KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > taglibs > standard > tag > common > core > ImportSupport


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.taglibs.standard.tag.common.core;
18
19 import java.io.BufferedReader JavaDoc;
20 import java.io.ByteArrayOutputStream JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.io.InputStream JavaDoc;
23 import java.io.InputStreamReader JavaDoc;
24 import java.io.PrintWriter JavaDoc;
25 import java.io.Reader JavaDoc;
26 import java.io.StringReader JavaDoc;
27 import java.io.StringWriter JavaDoc;
28 import java.io.UnsupportedEncodingException JavaDoc;
29 import java.net.HttpURLConnection JavaDoc;
30 import java.net.URL JavaDoc;
31 import java.net.URLConnection JavaDoc;
32 import java.util.Locale JavaDoc;
33
34 import javax.servlet.RequestDispatcher JavaDoc;
35 import javax.servlet.ServletContext JavaDoc;
36 import javax.servlet.ServletException JavaDoc;
37 import javax.servlet.ServletOutputStream JavaDoc;
38 import javax.servlet.http.HttpServletRequest JavaDoc;
39 import javax.servlet.http.HttpServletResponse JavaDoc;
40 import javax.servlet.http.HttpServletResponseWrapper JavaDoc;
41 import javax.servlet.jsp.JspException JavaDoc;
42 import javax.servlet.jsp.JspTagException JavaDoc;
43 import javax.servlet.jsp.PageContext JavaDoc;
44 import javax.servlet.jsp.tagext.BodyTagSupport JavaDoc;
45 import javax.servlet.jsp.tagext.TryCatchFinally JavaDoc;
46
47 import org.apache.taglibs.standard.resources.Resources;
48
49 /**
50  * <p>Support for tag handlers for &lt;import&gt;, the general-purpose
51  * text-importing mechanism for JSTL 1.0. The rtexprvalue and expression-
52  * evaluating libraries each have handlers that extend this class.</p>
53  *
54  * @author Shawn Bayern
55  */

56
57 public abstract class ImportSupport extends BodyTagSupport JavaDoc
58         implements TryCatchFinally JavaDoc, ParamParent {
59
60     //*********************************************************************
61
// Public constants
62

63     /** <p>Valid characters in a scheme.</p>
64      * <p>RFC 1738 says the following:</p>
65      * <blockquote>
66      * Scheme names consist of a sequence of characters. The lower
67      * case letters "a"--"z", digits, and the characters plus ("+"),
68      * period ("."), and hyphen ("-") are allowed. For resiliency,
69      * programs interpreting URLs should treat upper case letters as
70      * equivalent to lower case in scheme names (e.g., allow "HTTP" as
71      * well as "http").
72      * </blockquote>
73      * <p>We treat as absolute any URL that begins with such a scheme name,
74      * followed by a colon.</p>
75      */

76     public static final String JavaDoc VALID_SCHEME_CHARS =
77     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-";
78
79     /** Default character encoding for response. */
80     public static final String JavaDoc DEFAULT_ENCODING = "ISO-8859-1";
81
82     //*********************************************************************
83
// Protected state
84

85     protected String JavaDoc url; // 'url' attribute
86
protected String JavaDoc context; // 'context' attribute
87
protected String JavaDoc charEncoding; // 'charEncoding' attrib.
88

89     //*********************************************************************
90
// Private state (implementation details)
91

92     private String JavaDoc var; // 'var' attribute
93
private int scope; // processed 'scope' attribute
94
private String JavaDoc varReader; // 'varReader' attribute
95
private Reader JavaDoc r; // exposed reader, if relevant
96
private boolean isAbsoluteUrl; // is our URL absolute?
97
private ParamSupport.ParamManager params; // parameters
98
private String JavaDoc urlWithParams; // URL with parameters, if applicable
99

100     //*********************************************************************
101
// Constructor and initialization
102

103     public ImportSupport() {
104     super();
105     init();
106     }
107
108     private void init() {
109     url = var = varReader = context = charEncoding = urlWithParams = null;
110     params = null;
111         scope = PageContext.PAGE_SCOPE;
112     }
113
114
115     //*********************************************************************
116
// Tag logic
117

118     // determines what kind of import and variable exposure to perform
119
public int doStartTag() throws JspException JavaDoc {
120     // Sanity check
121
if (context != null
122             && (!context.startsWith("/") || !url.startsWith("/"))) {
123         throw new JspTagException JavaDoc(
124         Resources.getMessage("IMPORT_BAD_RELATIVE"));
125     }
126
127     // reset parameter-related state
128
urlWithParams = null;
129     params = new ParamSupport.ParamManager();
130
131     // check the URL
132
if (url == null || url.equals(""))
133         throw new NullAttributeException("import", "url");
134
135     // Record whether our URL is absolute or relative
136
isAbsoluteUrl = isAbsoluteUrl();
137
138     try {
139         // If we need to expose a Reader, we've got to do it right away
140
if (varReader != null) {
141             r = acquireReader();
142             pageContext.setAttribute(varReader, r);
143         }
144     } catch (IOException JavaDoc ex) {
145         throw new JspTagException JavaDoc(ex.toString(), ex);
146     }
147
148     return EVAL_BODY_INCLUDE;
149     }
150
151     // manages connections as necessary (creating or destroying)
152
public int doEndTag() throws JspException JavaDoc {
153         try {
154         // If we didn't expose a Reader earlier...
155
if (varReader == null) {
156             // ... store it in 'var', if available ...
157
if (var != null)
158                 pageContext.setAttribute(var, acquireString(), scope);
159                 // ... or simply output it, if we have nowhere to expose it
160
else
161                 pageContext.getOut().print(acquireString());
162         }
163         return EVAL_PAGE;
164         } catch (IOException JavaDoc ex) {
165         throw new JspTagException JavaDoc(ex.toString(), ex);
166         }
167     }
168
169     // simply rethrows its exception
170
public void doCatch(Throwable JavaDoc t) throws Throwable JavaDoc {
171     throw t;
172     }
173
174     // cleans up if appropriate
175
public void doFinally() {
176         try {
177         // If we exposed a Reader in doStartTag(), close it.
178
if (varReader != null) {
179         // 'r' can be null if an exception was thrown...
180
if (r != null)
181             r.close();
182         pageContext.removeAttribute(varReader, PageContext.PAGE_SCOPE);
183         }
184         } catch (IOException JavaDoc ex) {
185         // ignore it; close() failed, but there's nothing more we can do
186
}
187     }
188
189     // Releases any resources we may have (or inherit)
190
public void release() {
191     init();
192         super.release();
193     }
194
195     //*********************************************************************
196
// Tag attributes known at translation time
197

198     public void setVar(String JavaDoc var) {
199     this.var = var;
200     }
201
202     public void setVarReader(String JavaDoc varReader) {
203     this.varReader = varReader;
204     }
205
206     public void setScope(String JavaDoc scope) {
207     this.scope = Util.getScope(scope);
208     }
209
210
211     //*********************************************************************
212
// Collaboration with subtags
213

214     // inherit Javadoc
215
public void addParameter(String JavaDoc name, String JavaDoc value) {
216     params.addParameter(name, value);
217     }
218
219     //*********************************************************************
220
// Actual URL importation logic
221

222     /*
223      * Overall strategy: we have two entry points, acquireString() and
224      * acquireReader(). The latter passes data through unbuffered if
225      * possible (but note that it is not always possible -- specifically
226      * for cases where we must use the RequestDispatcher. The remaining
227      * methods handle the common.core logic of loading either a URL or a local
228      * resource.
229      *
230      * We consider the 'natural' form of absolute URLs to be Readers and
231      * relative URLs to be Strings. Thus, to avoid doing extra work,
232      * acquireString() and acquireReader() delegate to one another as
233      * appropriate. (Perhaps I could have spelled things out more clearly,
234      * but I thought this implementation was instructive, not to mention
235      * somewhat cute...)
236      */

237
238     private String JavaDoc acquireString() throws IOException JavaDoc, JspException JavaDoc {
239     if (isAbsoluteUrl) {
240         // for absolute URLs, delegate to our peer
241
BufferedReader JavaDoc r = new BufferedReader JavaDoc(acquireReader());
242         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
243         int i;
244
245         // under JIT, testing seems to show this simple loop is as fast
246
// as any of the alternatives
247
while ((i = r.read()) != -1)
248             sb.append((char)i);
249
250         return sb.toString();
251     } else {
252         // handle relative URLs ourselves
253

254         // URL is relative, so we must be an HTTP request
255
if (!(pageContext.getRequest() instanceof HttpServletRequest JavaDoc
256           && pageContext.getResponse() instanceof HttpServletResponse JavaDoc))
257         throw new JspTagException JavaDoc(
258             Resources.getMessage("IMPORT_REL_WITHOUT_HTTP"));
259
260         // retrieve an appropriate ServletContext
261
ServletContext JavaDoc c = null;
262         String JavaDoc targetUrl = targetUrl();
263         if (context != null)
264             c = pageContext.getServletContext().getContext(context);
265         else {
266             c = pageContext.getServletContext();
267
268         // normalize the URL if we have an HttpServletRequest
269
if (!targetUrl.startsWith("/")) {
270             String JavaDoc sp = ((HttpServletRequest JavaDoc)
271             pageContext.getRequest()).getServletPath();
272             targetUrl = sp.substring(0, sp.lastIndexOf('/'))
273             + '/' + targetUrl;
274         }
275         }
276
277             if (c == null) {
278                 throw new JspTagException JavaDoc(
279                     Resources.getMessage(
280                         "IMPORT_REL_WITHOUT_DISPATCHER", context, targetUrl));
281             }
282
283         // from this context, get a dispatcher
284
RequestDispatcher JavaDoc rd =
285                 c.getRequestDispatcher(stripSession(targetUrl));
286         if (rd == null)
287         throw new JspTagException JavaDoc(stripSession(targetUrl));
288
289         // include the resource, using our custom wrapper
290
ImportResponseWrapper irw =
291         new ImportResponseWrapper(
292             (HttpServletResponse JavaDoc) pageContext.getResponse());
293
294         // spec mandates specific error handling form include()
295
try {
296             rd.include(pageContext.getRequest(), irw);
297         } catch (IOException JavaDoc ex) {
298         throw new JspException JavaDoc(ex);
299         } catch (RuntimeException JavaDoc ex) {
300         throw new JspException JavaDoc(ex);
301         } catch (ServletException JavaDoc ex) {
302         Throwable JavaDoc rc = ex.getRootCause();
303         if (rc == null)
304             throw new JspException JavaDoc(ex);
305         else
306             throw new JspException JavaDoc(rc);
307         }
308
309         // disallow inappropriate response codes per JSTL spec
310
if (irw.getStatus() < 200 || irw.getStatus() > 299) {
311         throw new JspTagException JavaDoc(irw.getStatus() + " " +
312             stripSession(targetUrl));
313         }
314
315         // recover the response String from our wrapper
316
return irw.getString();
317     }
318     }
319
320     private Reader JavaDoc acquireReader() throws IOException JavaDoc, JspException JavaDoc {
321     if (!isAbsoluteUrl) {
322         // for relative URLs, delegate to our peer
323
return new StringReader JavaDoc(acquireString());
324     } else {
325             // absolute URL
326
String JavaDoc target = targetUrl();
327         try {
328             // handle absolute URLs ourselves, using java.net.URL
329
URL JavaDoc u = new URL JavaDoc(target);
330                 URLConnection JavaDoc uc = u.openConnection();
331                 InputStream JavaDoc i = uc.getInputStream();
332
333             // okay, we've got a stream; encode it appropriately
334
Reader JavaDoc r = null;
335                 String JavaDoc charSet;
336             if (charEncoding != null && !charEncoding.equals("")) {
337                     charSet = charEncoding;
338                 } else {
339                     // charSet extracted according to RFC 2045, section 5.1
340
String JavaDoc contentType = uc.getContentType();
341             if (contentType != null) {
342                         charSet = Util.getContentTypeAttribute(contentType, "charset");
343                         if (charSet == null) charSet = DEFAULT_ENCODING;
344                     } else {
345                         charSet = DEFAULT_ENCODING;
346                     }
347                 }
348                 try {
349                     r = new InputStreamReader JavaDoc(i, charSet);
350                 } catch (Exception JavaDoc ex) {
351                     r = new InputStreamReader JavaDoc(i, DEFAULT_ENCODING);
352                 }
353
354         // check response code for HTTP URLs before returning, per spec,
355
// before returning
356
if (uc instanceof HttpURLConnection JavaDoc) {
357             int status = ((HttpURLConnection JavaDoc) uc).getResponseCode();
358             if (status < 200 || status > 299)
359             throw new JspTagException JavaDoc(status + " " + target);
360         }
361
362             return r;
363         } catch (IOException JavaDoc ex) {
364         throw new JspException JavaDoc(
365                     Resources.getMessage("IMPORT_ABS_ERROR", target, ex), ex);
366         } catch (RuntimeException JavaDoc ex) { // because the spec makes us
367
throw new JspException JavaDoc(
368                     Resources.getMessage("IMPORT_ABS_ERROR", target, ex), ex);
369         }
370     }
371     }
372
373     /** Wraps responses to allow us to retrieve results as Strings. */
374     private class ImportResponseWrapper extends HttpServletResponseWrapper JavaDoc {
375
376     //************************************************************
377
// Overview
378

379     /*
380      * We provide either a Writer or an OutputStream as requested.
381      * We actually have a true Writer and an OutputStream backing
382      * both, since we don't want to use a character encoding both
383      * ways (Writer -> OutputStream -> Writer). So we use no
384      * encoding at all (as none is relevant) when the target resource
385      * uses a Writer. And we decode the OutputStream's bytes
386      * using OUR tag's 'charEncoding' attribute, or ISO-8859-1
387      * as the default. We thus ignore setLocale() and setContentType()
388      * in this wrapper.
389      *
390      * In other words, the target's asserted encoding is used
391      * to convert from a Writer to an OutputStream, which is typically
392      * the medium through with the target will communicate its
393      * ultimate response. Since we short-circuit that mechanism
394      * and read the target's characters directly if they're offered
395      * as such, we simply ignore the target's encoding assertion.
396      */

397
398     //************************************************************
399
// Data
400

401     /** The Writer we convey. */
402     private StringWriter JavaDoc sw = new StringWriter JavaDoc();
403
404     /** A buffer, alternatively, to accumulate bytes. */
405     private ByteArrayOutputStream JavaDoc bos = new ByteArrayOutputStream JavaDoc();
406
407     /** A ServletOutputStream we convey, tied to this Writer. */
408     private ServletOutputStream JavaDoc sos = new ServletOutputStream JavaDoc() {
409         public void write(int b) throws IOException JavaDoc {
410         bos.write(b);
411         }
412     };
413
414     /** 'True' if getWriter() was called; false otherwise. */
415     private boolean isWriterUsed;
416
417     /** 'True if getOutputStream() was called; false otherwise. */
418     private boolean isStreamUsed;
419
420     /** The HTTP status set by the target. */
421     private int status = 200;
422     
423     //************************************************************
424
// Constructor and methods
425

426     /** Constructs a new ImportResponseWrapper. */
427     public ImportResponseWrapper(HttpServletResponse JavaDoc response) {
428         super(response);
429     }
430     
431     /** Returns a Writer designed to buffer the output. */
432     public PrintWriter JavaDoc getWriter() {
433         if (isStreamUsed)
434         throw new IllegalStateException JavaDoc(
435             Resources.getMessage("IMPORT_ILLEGAL_STREAM"));
436         isWriterUsed = true;
437         return new PrintWriter JavaDoc(sw);
438     }
439     
440     /** Returns a ServletOutputStream designed to buffer the output. */
441     public ServletOutputStream JavaDoc getOutputStream() {
442         if (isWriterUsed)
443         throw new IllegalStateException JavaDoc(
444             Resources.getMessage("IMPORT_ILLEGAL_WRITER"));
445         isStreamUsed = true;
446         return sos;
447     }
448
449     /** Has no effect. */
450     public void setContentType(String JavaDoc x) {
451         // ignore
452
}
453
454     /** Has no effect. */
455     public void setLocale(Locale JavaDoc x) {
456         // ignore
457
}
458
459     public void setStatus(int status) {
460         this.status = status;
461     }
462
463     public int getStatus() {
464         return status;
465     }
466
467     /**
468      * Retrieves the buffered output, using the containing tag's
469      * 'charEncoding' attribute, or the tag's default encoding,
470      * <b>if necessary</b>.
471          */

472     // not simply toString() because we need to throw
473
// UnsupportedEncodingException
474
public String JavaDoc getString() throws UnsupportedEncodingException JavaDoc {
475         if (isWriterUsed)
476         return sw.toString();
477         else if (isStreamUsed) {
478         if (charEncoding != null && !charEncoding.equals(""))
479             return bos.toString(charEncoding);
480         else
481             return bos.toString(DEFAULT_ENCODING);
482         } else
483         return ""; // target didn't write anything
484
}
485     }
486
487     //*********************************************************************
488
// Some private utility methods
489

490     /** Returns our URL (potentially with parameters) */
491     private String JavaDoc targetUrl() {
492     if (urlWithParams == null)
493         urlWithParams = params.aggregateParams(url);
494     return urlWithParams;
495     }
496
497     /**
498      * Returns <tt>true</tt> if our current URL is absolute,
499      * <tt>false</tt> otherwise.
500      */

501     private boolean isAbsoluteUrl() throws JspTagException JavaDoc {
502         return isAbsoluteUrl(url);
503     }
504
505
506     //*********************************************************************
507
// Public utility methods
508

509     /**
510      * Returns <tt>true</tt> if our current URL is absolute,
511      * <tt>false</tt> otherwise.
512      */

513     public static boolean isAbsoluteUrl(String JavaDoc url) {
514     // a null URL is not absolute, by our definition
515
if (url == null)
516         return false;
517
518     // do a fast, simple check first
519
int colonPos;
520     if ((colonPos = url.indexOf(":")) == -1)
521         return false;
522
523     // if we DO have a colon, make sure that every character
524
// leading up to it is a valid scheme character
525
for (int i = 0; i < colonPos; i++)
526         if (VALID_SCHEME_CHARS.indexOf(url.charAt(i)) == -1)
527         return false;
528
529     // if so, we've got an absolute url
530
return true;
531     }
532
533     /**
534      * Strips a servlet session ID from <tt>url</tt>. The session ID
535      * is encoded as a URL "path parameter" beginning with "jsessionid=".
536      * We thus remove anything we find between ";jsessionid=" (inclusive)
537      * and either EOS or a subsequent ';' (exclusive).
538      */

539     public static String JavaDoc stripSession(String JavaDoc url) {
540     StringBuffer JavaDoc u = new StringBuffer JavaDoc(url);
541         int sessionStart;
542         while ((sessionStart = u.toString().indexOf(";jsessionid=")) != -1) {
543             int sessionEnd = u.toString().indexOf(";", sessionStart + 1);
544             if (sessionEnd == -1)
545         sessionEnd = u.toString().indexOf("?", sessionStart + 1);
546         if (sessionEnd == -1) // still
547
sessionEnd = u.length();
548             u.delete(sessionStart, sessionEnd);
549         }
550         return u.toString();
551     }
552 }
553
Popular Tags