You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by ma...@apache.org on 2003/10/30 01:02:35 UTC
cvs commit: jakarta-velocity-tools/src/java/org/apache/velocity/tools/view ImportSupport.java
marino 2003/10/29 16:02:35
Added: src/java/org/apache/velocity/tools/view ImportSupport.java
Log:
an abstract class that provides methods to import arbitrary resources, local or external, as strings.
Revision Changes Path
1.1 jakarta-velocity-tools/src/java/org/apache/velocity/tools/view/ImportSupport.java
Index: ImportSupport.java
===================================================================
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Velocity", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.velocity.tools.view;
import java.util.Stack;
import java.util.Map;
import java.util.Locale;
import java.util.List;
import java.util.LinkedList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletException;
import java.io.InputStream;
import java.io.Reader;
import java.io.BufferedReader;
import java.io.StringReader;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.PrintWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;
/**
* <p>Title: ImportSupport</p>
* <p>Description: provides methods to import arbitrary resources as String</p>
*
* @author <a href="mailto:marinoj@centrum.is">Marino A. Jonsson</a>
* @version $Revision: 1.1 $ $Date: 2003/10/30 00:02:35 $
*/
public abstract class ImportSupport {
protected ServletContext application;
protected HttpServletRequest request;
protected HttpServletResponse response;
protected boolean isAbsoluteUrl; // is our URL absolute?
protected static final String VALID_SCHEME_CHARS =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-";
/** Default character encoding for response. */
protected static final String DEFAULT_ENCODING = "ISO-8859-1";
//*********************************************************************
// URL importation logic
/*
* Overall strategy: we have two entry points, acquireString() and
* acquireReader(). The latter passes data through unbuffered if
* possible (but note that it is not always possible -- specifically
* for cases where we must use the RequestDispatcher. The remaining
* methods handle the common.core logic of loading either a URL or a local
* resource.
*
* We consider the 'natural' form of absolute URLs to be Readers and
* relative URLs to be Strings. Thus, to avoid doing extra work,
* acquireString() and acquireReader() delegate to one another as
* appropriate. (Perhaps I could have spelled things out more clearly,
* but I thought this implementation was instructive, not to mention
* somewhat cute...)
*/
/**
*
* @param url the URL resource to return as string
* @return the URL resource as string
* @throws IOException
* @throws java.lang.Exception
*/
protected String acquireString(String url) throws IOException, Exception {
// Record whether our URL is absolute or relative
isAbsoluteUrl = isAbsoluteUrl(url);
if (isAbsoluteUrl) {
// for absolute URLs, delegate to our peer
BufferedReader r = new BufferedReader(acquireReader(url));
StringBuffer sb = new StringBuffer();
int i;
// under JIT, testing seems to show this simple loop is as fast
// as any of the alternatives
while ( (i = r.read()) != -1)
sb.append( (char) i);
return sb.toString();
}
else {
// handle relative URLs ourselves
// URL is relative, so we must be an HTTP request
if (! (request instanceof HttpServletRequest
&& response instanceof HttpServletResponse))
throw new Exception("Importing a non-HTTP relative resource");
// retrieve an appropriate ServletContext
// normalize the URL if we have an HttpServletRequest
if (!url.startsWith("/")) {
String sp = ( (HttpServletRequest) request).getServletPath();
url = sp.substring(0, sp.lastIndexOf('/')) + '/' + url;
}
// from this context, get a dispatcher
RequestDispatcher rd = application.getRequestDispatcher(stripSession(url));
if (rd == null)
throw new Exception(stripSession(url));
// include the resource, using our custom wrapper
ImportResponseWrapper irw = new ImportResponseWrapper( (HttpServletResponse) response);
// spec mandates specific error handling form include()
try {
rd.include(request, irw);
}
catch (IOException ex) {
throw new Exception(ex);
}
catch (RuntimeException ex) {
throw new Exception(ex);
}
catch (ServletException ex) {
Throwable rc = ex.getRootCause();
if (rc == null)
throw new Exception(ex);
else
throw new Exception(rc);
}
// disallow inappropriate response codes per JSTL spec
if (irw.getStatus() < 200 || irw.getStatus() > 299) {
throw new Exception(irw.getStatus() + " " + stripSession(url));
}
// recover the response String from our wrapper
return irw.getString();
}
}
/**
*
* @param url the URL to read
* @return a Reader for the InputStream created from the supplied URL
* @throws IOException
* @throws java.lang.Exception
*/
protected Reader acquireReader(String url) throws IOException, Exception {
if (!isAbsoluteUrl) {
// for relative URLs, delegate to our peer
return new StringReader(acquireString(url));
}
else {
// absolute URL
try {
// handle absolute URLs ourselves, using java.net.URL
URL u = new URL(url);
//URL u = new URL("http", "proxy.hi.is", 8080, target);
URLConnection uc = u.openConnection();
InputStream i = uc.getInputStream();
// okay, we've got a stream; encode it appropriately
Reader r = null;
String charSet;
// charSet extracted according to RFC 2045, section 5.1
String contentType = uc.getContentType();
if (contentType != null) {
charSet = this.getContentTypeAttribute(contentType, "charset");
if (charSet == null)
charSet = DEFAULT_ENCODING;
}
else {
charSet = DEFAULT_ENCODING;
}
try {
r = new InputStreamReader(i, charSet);
}
catch (Exception ex) {
r = new InputStreamReader(i, DEFAULT_ENCODING);
}
// check response code for HTTP URLs before returning, per spec,
// before returning
if (uc instanceof HttpURLConnection) {
int status = ( (HttpURLConnection) uc).getResponseCode();
if (status < 200 || status > 299)
throw new Exception(status + " " + url);
}
return r;
}
catch (IOException ex) {
throw new Exception("IMPORT_ABS_ERROR", ex);
}
catch (RuntimeException ex) { // because the spec makes us
throw new Exception("IMPORT_ABS_ERROR", ex);
}
}
}
/** Wraps responses to allow us to retrieve results as Strings. */
protected class ImportResponseWrapper
extends HttpServletResponseWrapper {
/*
* We provide either a Writer or an OutputStream as requested.
* We actually have a true Writer and an OutputStream backing
* both, since we don't want to use a character encoding both
* ways (Writer -> OutputStream -> Writer). So we use no
* encoding at all (as none is relevant) when the target resource
* uses a Writer. And we decode the OutputStream's bytes
* using OUR tag's 'charEncoding' attribute, or ISO-8859-1
* as the default. We thus ignore setLocale() and setContentType()
* in this wrapper.
*
* In other words, the target's asserted encoding is used
* to convert from a Writer to an OutputStream, which is typically
* the medium through with the target will communicate its
* ultimate response. Since we short-circuit that mechanism
* and read the target's characters directly if they're offered
* as such, we simply ignore the target's encoding assertion.
*/
/** The Writer we convey. */
private StringWriter sw = new StringWriter();
/** A buffer, alternatively, to accumulate bytes. */
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
/** A ServletOutputStream we convey, tied to this Writer. */
private ServletOutputStream sos = new ServletOutputStream() {
public void write(int b) throws IOException {
bos.write(b);
}
};
/** 'True' if getWriter() was called; false otherwise. */
private boolean isWriterUsed;
/** 'True if getOutputStream() was called; false otherwise. */
private boolean isStreamUsed;
/** The HTTP status set by the target. */
private int status = 200;
//************************************************************
// Constructor and methods
/** Constructs a new ImportResponseWrapper.
* @param response the response to wrap
*/
public ImportResponseWrapper(HttpServletResponse response) {
super(response);
}
/**
* @return a Writer designed to buffer the output.
*/
public PrintWriter getWriter() {
if (isStreamUsed)
throw new IllegalStateException("IMPORT_ILLEGAL_STREAM");
isWriterUsed = true;
return new PrintWriter(sw);
}
/**
* @return a ServletOutputStream designed to buffer the output.
*/
public ServletOutputStream getOutputStream() {
if (isWriterUsed)
throw new IllegalStateException("IMPORT_ILLEGAL_WRITER");
isStreamUsed = true;
return sos;
}
/** Has no effect.
* @param x never mind :)
*/
public void setContentType(String x) {
// ignore
}
/** Has no effect.
* @param x never mind :)
*/
public void setLocale(Locale x) {
// ignore
}
/** Sets the status of the response
* @param status the status code
*/
public void setStatus(int status) {
this.status = status;
}
/**
* @return the status of the response
*/
public int getStatus() {
return status;
}
/**
* Retrieves the buffered output, using the containing tag's
* 'charEncoding' attribute, or the tag's default encoding,
* <b>if necessary</b>.
* @return the buffered output
* @throws UnsupportedEncodingException if the encoding is not supported
*/
public String getString() throws UnsupportedEncodingException {
if (isWriterUsed)
return sw.toString();
else if (isStreamUsed) {
return bos.toString(DEFAULT_ENCODING);
}
else
return ""; // target didn't write anything
}
}
//*********************************************************************
// Public utility methods
/**
* Returns <tt>true</tt> if our current URL is absolute,
* <tt>false</tt> otherwise.
*
* @param url the url to check out
* @return true if the url is absolute
*/
public static boolean isAbsoluteUrl(String url) {
// a null URL is not absolute, by our definition
if (url == null)
return false;
// do a fast, simple check first
int colonPos;
if ( (colonPos = url.indexOf(":")) == -1)
return false;
// if we DO have a colon, make sure that every character
// leading up to it is a valid scheme character
for (int i = 0; i < colonPos; i++)
if (VALID_SCHEME_CHARS.indexOf(url.charAt(i)) == -1)
return false;
// if so, we've got an absolute url
return true;
}
/**
* Strips a servlet session ID from <tt>url</tt>. The session ID
* is encoded as a URL "path parameter" beginning with "jsessionid=".
* We thus remove anything we find between ";jsessionid=" (inclusive)
* and either EOS or a subsequent ';' (exclusive).
*
* @param url the url to strip the session id from
* @return the stripped url
*/
public static String stripSession(String url) {
StringBuffer u = new StringBuffer(url);
int sessionStart;
while ( (sessionStart = u.toString().indexOf(";jsessionid=")) != -1) {
int sessionEnd = u.toString().indexOf(";", sessionStart + 1);
if (sessionEnd == -1)
sessionEnd = u.toString().indexOf("?", sessionStart + 1);
if (sessionEnd == -1) // still
sessionEnd = u.length();
u.delete(sessionStart, sessionEnd);
}
return u.toString();
}
/**
* Get the value associated with a content-type attribute.
* Syntax defined in RFC 2045, section 5.1.
*
* @param input the string containing the attributes
* @param name the name of the content-type attribute
* @return the value associated with a content-type attribute
*/
public static String getContentTypeAttribute(String input, String name) {
int begin;
int end;
int index = input.toUpperCase().indexOf(name.toUpperCase());
if (index == -1)
return null;
index = index + name.length(); // positioned after the attribute name
index = input.indexOf('=', index); // positioned at the '='
if (index == -1)
return null;
index += 1; // positioned after the '='
input = input.substring(index).trim();
if (input.charAt(0) == '"') {
// attribute value is a quoted string
begin = 1;
end = input.indexOf('"', begin);
if (end == -1)
return null;
}
else {
begin = 0;
end = input.indexOf(';');
if (end == -1)
end = input.indexOf(' ');
if (end == -1)
end = input.length();
}
return input.substring(begin, end).trim();
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: velocity-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: velocity-dev-help@jakarta.apache.org