Renamed servlet to own groupId. Fixed naming of ImageIO

This commit is contained in:
Erlend Hamnaberg
2010-02-07 19:17:13 +01:00
parent abdecf1d5e
commit 3050899903
123 changed files with 16 additions and 16 deletions

123
servlet/pom.xml Normal file
View File

@@ -0,0 +1,123 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-parent</artifactId>
<version>3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.0-SNAPSHOT</version>
<name>TwelveMonkeys :: Servlet</name>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-image</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-lang</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common-io</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jmock</groupId>
<artifactId>jmock-cglib</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<Implementation-Title>${project.name}</Implementation-Title>
<Implementation-Vendor>TwelveMonkeys</Implementation-Vendor>
<Implementation-Version>${project.version}</Implementation-Version>
<Implementation-URL>http://github.com/haraldk/TwelveMonkeys</Implementation-URL>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,149 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.util.CollectionUtil;
import java.util.*;
/**
* AbstractServletMapAdapter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/AbstractServletMapAdapter.java#1 $
*/
abstract class AbstractServletMapAdapter extends AbstractMap<String, List<String>> {
// TODO: This map is now a little too lazy.. Should cache entries too (instead?) !
private final static List<String> NULL_LIST = new ArrayList<String>();
private transient Map<String, List<String>> mCache = new HashMap<String, List<String>>();
private transient int mSize = -1;
private transient AbstractSet<Entry<String, List<String>>> mEntries;
protected abstract Iterator<String> keysImpl();
protected abstract Iterator<String> valuesImpl(String pName);
@Override
public List<String> get(Object pKey) {
if (pKey instanceof String) {
return getValues((String) pKey);
}
return null;
}
private List<String> getValues(String pName) {
List<String> values = mCache.get(pName);
if (values == null) {
//noinspection unchecked
Iterator<String> headers = valuesImpl(pName);
if (headers == null) {
mCache.put(pName, NULL_LIST);
}
else {
values = toList(headers);
mCache.put(pName, values);
}
}
return values == NULL_LIST ? null : values;
}
private static List<String> toList(final Iterator<String> pValues) {
List<String> list = new ArrayList<String>();
CollectionUtil.addAll(list, pValues);
return Collections.unmodifiableList(list);
}
@Override
public int size() {
if (mSize == -1) {
computeSize();
}
return mSize;
}
private void computeSize() {
Iterator<String> names = keysImpl();
mSize = 0;
for (;names.hasNext(); names.next()) {
mSize++;
}
}
public Set<Entry<String, List<String>>> entrySet() {
if (mEntries == null) {
mEntries = new AbstractSet<Entry<String, List<String>>>() {
public Iterator<Entry<String, List<String>>> iterator() {
return new Iterator<Entry<String, List<String>>>() {
Iterator<String> mHeaderNames = keysImpl();
public boolean hasNext() {
return mHeaderNames.hasNext();
}
public Entry<String, List<String>> next() {
// TODO: Replace with cached lookup
return new HeaderEntry(mHeaderNames.next());
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
return AbstractServletMapAdapter.this.size();
}
};
}
return mEntries;
}
private class HeaderEntry implements Entry<String, List<String>> {
String mHeaderName;
public HeaderEntry(String pHeaderName) {
mHeaderName = pHeaderName;
}
public String getKey() {
return mHeaderName;
}
public List<String> getValue() {
return get(mHeaderName);
}
public List<String> setValue(List<String> pValue) {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
List<String> value;
return (mHeaderName == null ? 0 : mHeaderName.hashCode()) ^
((value = getValue()) == null ? 0 : value.hashCode());
}
@Override
public boolean equals(Object pOther) {
if (pOther == this) {
return true;
}
if (pOther instanceof Entry) {
Entry other = (Entry) pOther;
return ((other.getKey() == null && getKey() == null) ||
(getKey() != null && getKey().equals(other.getKey()))) &&
((other.getValue() == null && getValue() == null) ||
(getValue() != null && getValue().equals(other.getValue())));
}
return false;
}
}
}

View File

@@ -0,0 +1,151 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Properties;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* BrowserHelperFilter
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/BrowserHelperFilter.java#1 $
*/
public class BrowserHelperFilter extends GenericFilter {
private static final String HTTP_HEADER_ACCEPT = "Accept";
protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
private Pattern[] mKnownAgentPatterns;
private String[] mKnownAgentAccpets;
/**
* Sets the accept-mappings for this filter
* @param pPropertiesFile name of accept-mappings properties files
* @throws ServletConfigException if the accept-mappings properties
* file cannot be read.
*/
public void setAcceptMappingsFile(String pPropertiesFile) throws ServletConfigException {
// NOTE: Format is:
// <agent-name>=<reg-exp>
// <agent-name>.accept=<http-accept-header>
Properties mappings = new Properties();
try {
log("Reading Accept-mappings properties file: " + pPropertiesFile);
mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
List<Pattern> patterns = new ArrayList<Pattern>();
List<String> accepts = new ArrayList<String>();
//System.out.println("--> Loaded file: " + pPropertiesFile);
for (Object key : mappings.keySet()) {
String agent = (String) key;
if (agent.endsWith(".accept")) {
continue;
}
//System.out.println("--> Adding accept-mapping for User-Agent: " + agent);
try {
String accept = (String) mappings.get(agent + ".accept");
if (!StringUtil.isEmpty(accept)) {
patterns.add(Pattern.compile((String) mappings.get(agent)));
accepts.add(accept);
//System.out.println("--> " + agent + " accepts: " + accept);
}
else {
log("Missing Accept mapping for User-Agent: " + agent);
}
}
catch (PatternSyntaxException e) {
log("Could not parse User-Agent identification for " + agent, e);
}
mKnownAgentPatterns = patterns.toArray(new Pattern[patterns.size()]);
mKnownAgentAccpets = accepts.toArray(new String[accepts.size()]);
}
}
catch (IOException e) {
throw new ServletConfigException("Could not read Accept-mappings properties file: " + pPropertiesFile, e);
}
}
public void init() throws ServletException {
if (mKnownAgentAccpets == null || mKnownAgentAccpets.length == 0) {
throw new ServletConfigException("No User-Agent/Accept mappings for filter: " + getFilterName());
}
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
if (pRequest instanceof HttpServletRequest) {
//System.out.println("--> Trying to find User-Agent/Accept headers...");
HttpServletRequest request = (HttpServletRequest) pRequest;
// Check if User-Agent is in list of known agents
if (mKnownAgentPatterns != null && mKnownAgentPatterns.length > 0) {
String agent = request.getHeader(HTTP_HEADER_USER_AGENT);
//System.out.println("--> User-Agent: " + agent);
for (int i = 0; i < mKnownAgentPatterns.length; i++) {
Pattern pattern = mKnownAgentPatterns[i];
//System.out.println("--> Pattern: " + pattern);
if (pattern.matcher(agent).matches()) {
// TODO: Consider merge known with real accpet, in case plugins add extra capabilities?
final String fakeAccept = mKnownAgentAccpets[i];
//System.out.println("--> User-Agent: " + agent + " accepts: " + fakeAccept);
pRequest = new HttpServletRequestWrapper(request) {
public String getHeader(String pName) {
if (HTTP_HEADER_ACCEPT.equals(pName)) {
return fakeAccept;
}
return super.getHeader(pName);
}
};
break;
}
}
}
}
pChain.doFilter(pRequest, pResponse);
}
}

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
/**
* DebugServlet class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java#1 $
*/
public class DebugServlet extends GenericServlet {
private long mDateModified;
public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
}
public void init() throws ServletException {
super.init();
mDateModified = System.currentTimeMillis();
}
public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
pResponse.setContentType("text/plain");
// Include these to allow browser caching
pResponse.setDateHeader("Last-Modified", mDateModified);
pResponse.setHeader("ETag", getServletName());
ServletOutputStream out = pResponse.getOutputStream();
out.println("Remote address: " + pRequest.getRemoteAddr());
out.println("Remote host name: " + pRequest.getRemoteHost());
out.println("Remote user: " + pRequest.getRemoteUser());
out.println();
out.println("Request Method: " + pRequest.getMethod());
out.println("Request Scheme: " + pRequest.getScheme());
out.println("Request URI: " + pRequest.getRequestURI());
out.println("Request URL: " + pRequest.getRequestURL().toString());
out.println("Request PathInfo: " + pRequest.getPathInfo());
out.println("Request ContentLength: " + pRequest.getContentLength());
out.println();
out.println("Request Headers:");
Enumeration headerNames = pRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
Enumeration headerValues = pRequest.getHeaders(headerName);
if (headerName != null) {
while (headerValues.hasMoreElements()) {
String value = (String) headerValues.nextElement();
out.println(" " + headerName + ": " + value);
}
}
}
out.println();
out.println("Request parameters:");
Enumeration paramNames = pRequest.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
String[] values = pRequest.getParameterValues(name);
for (String value : values) {
out.println(" " + name + ": " + value);
}
}
out.println();
out.println("Request attributes:");
Enumeration attribNames = pRequest.getAttributeNames();
while (attribNames.hasMoreElements()) {
String name = (String) attribNames.nextElement();
Object value = pRequest.getAttribute(name);
out.println(" " + name + ": " + value);
}
out.flush();
}
}

View File

@@ -0,0 +1,383 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import javax.servlet.*;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
/**
* Defines a generic, protocol-independent filter.
* <P/>
* {@code GenericFilter} is inspired by {@link GenericServlet}, and
* implements the {@code Filter} and {@code FilterConfig} interfaces.
* <P/>
* {@code GenericFilter} makes writing filters easier. It provides simple
* versions of the lifecycle methods {@code init} and {@code destroy}
* and of the methods in the {@code FilterConfig} interface.
* {@code GenericFilter} also implements the {@code log} methods,
* declared in the {@code ServletContext} interface.
* <p/
* {@code GenericFilter} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style paramter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string represenation to
* most basic types, if neccessary.
* <p/>
* To write a generic filter, you need only override the abstract
* {@link #doFilterImpl doFilterImpl} method.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java#1 $
*
* @see Filter
* @see FilterConfig
*/
public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
/**
* The filter config.
*/
private transient FilterConfig mFilterConfig = null;
/**
* Makes sure the filter runs once per request
* <p/>
* see #isRunOnce
*
* @see #mOncePerRequest
* see #ATTRIB_RUN_ONCE_VALUE
*/
private final static String ATTRIB_RUN_ONCE_EXT = ".REQUEST_HANDLED";
/**
* Makes sure the filter runs once per request.
* Must be configured through init method, as the filter name is not
* available before we have a FitlerConfig object.
* <p/>
* see #isRunOnce
*
* @see #mOncePerRequest
* see #ATTRIB_RUN_ONCE_VALUE
*/
private String mAttribRunOnce = null;
/**
* Makes sure the filter runs once per request
* <p/>
* see #isRunOnce
*
* @see #mOncePerRequest
* see #ATTRIB_RUN_ONCE_EXT
*/
private static final Object ATTRIB_RUN_ONCE_VALUE = new Object();
/**
* Indicates if this filter should run once per request ({@code true}),
* or for each forward/include resource ({@code false}).
* <p/>
* Set this variable to true, to make sure the filter runs once per request.
*
* <em>NOTE: As of Servlet 2.4, this field
* should always be left to it's default value ({@code false}).
* <br/>
* To run the filter once per request, the {@code filter-mapping} element
* of the web-descriptor should include a {@code dispatcher} element:
* <pre>&lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;</pre>
* </em>
*/
protected boolean mOncePerRequest = false;
/**
* Does nothing.
*/
public GenericFilter() {}
/**
* Called by the web container to indicate to a filter that it is being
* placed into service.
* <p/>
* This implementation stores the {@code FilterConfig} object it
* receives from the servlet container for later use.
* Generally, there's no reason to override this method, override the
* no-argument {@code init} instead. However, <em>if</em> you are
* overriding this form of the method,
* always call {@code super.init(config)}.
* <p/>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
*
* @param pConfig the filter config
* @throws ServletException if an error occurs during init
*
* @see Filter#init
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
public void init(FilterConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("filterconfig == null");
}
// Store filterconfig
mFilterConfig = pConfig;
// Configure this
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getFilterName(), e.getCause());
}
// Create run-once attribute name
mAttribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT;
log("init (oncePerRequest=" + mOncePerRequest + ", attribRunOnce=" + mAttribRunOnce + ")");
init();
}
/**
* A convenience method which can be overridden so that there's no need to
* call {@code super.init(config)}.
*
* @see #init(FilterConfig)
*
* @throws ServletException if an error occurs during init
*/
public void init() throws ServletException {}
/**
* The {@code doFilter} method of the Filter is called by the container
* each time a request/response pair is passed through the chain due to a
* client request for a resource at the end of the chain.
* <p/>
* Subclasses <em>should not override this method</em>, but rather the
* abstract {@link #doFilterImpl doFilterImpl} method.
*
* @param pRequest the servlet request
* @param pResponse the servlet response
* @param pFilterChain the filter chain
*
* @throws IOException
* @throws ServletException
*
* @see Filter#doFilter Filter.doFilter
* @see #doFilterImpl doFilterImpl
*/
public final void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException {
// If request filter and allready run, continue chain and return fast
if (mOncePerRequest && isRunOnce(pRequest)) {
pFilterChain.doFilter(pRequest, pResponse);
return;
}
// Do real filter
doFilterImpl(pRequest, pResponse, pFilterChain);
}
/**
* If request is filtered, returns true, otherwise marks request as filtered
* and returns false.
* A return value of false, indicates that the filter has not yet run.
* A return value of true, indicates that the filter has run for this
* request, and processing should not contine.
* <P/>
* Note that the method will mark the request as filtered on first
* invocation.
* <p/>
* see #ATTRIB_RUN_ONCE_EXT
* see #ATTRIB_RUN_ONCE_VALUE
*
* @param pRequest the servlet request
* @return {@code true} if the request is allready filtered, otherwise
* {@code false}.
*/
private boolean isRunOnce(ServletRequest pRequest) {
// If request allready filtered, return true (skip)
if (pRequest.getAttribute(mAttribRunOnce) == ATTRIB_RUN_ONCE_VALUE) {
return true;
}
// Set attribute and return false (continue)
pRequest.setAttribute(mAttribRunOnce, ATTRIB_RUN_ONCE_VALUE);
return false;
}
/**
* Invoked once, or each time a request/response pair is passed through the
* chain, depending on the {@link #mOncePerRequest} member variable.
*
* @param pRequest the servlet request
* @param pResponse the servlet response
* @param pChain the filter chain
*
* @throws IOException if an I/O error occurs
* @throws ServletException if an exception occurs during the filter process
*
* @see #mOncePerRequest
* @see #doFilter doFilter
* @see Filter#doFilter Filter.doFilter
*/
protected abstract void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException;
/**
* Called by the web container to indicate to a filter that it is being
* taken out of service.
*
* @see Filter#destroy
*/
public void destroy() {
log("destroy");
mFilterConfig = null;
}
/**
* Returns the filter-name of this filter as defined in the deployment
* descriptor.
*
* @return the filter-name
* @see FilterConfig#getFilterName
*/
public String getFilterName() {
return mFilterConfig.getFilterName();
}
/**
* Returns a reference to the {@link ServletContext} in which the caller is
* executing.
*
* @return the {@code ServletContext} object, used by the caller to
* interact with its servlet container
* @see FilterConfig#getServletContext
* @see ServletContext
*/
public ServletContext getServletContext() {
// TODO: Create a servlet context wrapper that lets you log to a log4j appender?
return mFilterConfig.getServletContext();
}
/**
* Returns a {@code String} containing the value of the named
* initialization parameter, or null if the parameter does not exist.
*
* @param pKey a {@code String} specifying the name of the
* initialization parameter
* @return a {@code String} containing the value of the initialization
* parameter
*/
public String getInitParameter(String pKey) {
return mFilterConfig.getInitParameter(pKey);
}
/**
* Returns the names of the servlet's initialization parameters as an
* {@code Enumeration} of {@code String} objects, or an empty
* {@code Enumeration} if the servlet has no initialization parameters.
*
* @return an {@code Enumeration} of {@code String} objects
* containing the mNames of the servlet's initialization parameters
*/
public Enumeration getInitParameterNames() {
return mFilterConfig.getInitParameterNames();
}
/**
* Writes the specified message to a servlet log file, prepended by the
* filter's name.
*
* @param pMessage the log message
* @see ServletContext#log(String)
*/
protected void log(String pMessage) {
getServletContext().log(getFilterName() + ": " + pMessage);
}
/**
* Writes an explanatory message and a stack trace for a given
* {@code Throwable} to the servlet log file, prepended by the
* filter's name.
*
* @param pMessage the log message
* @param pThrowable the exception
* @see ServletContext#log(String,Throwable)
*/
protected void log(String pMessage, Throwable pThrowable) {
getServletContext().log(getFilterName() + ": " + pMessage, pThrowable);
}
/**
* Initializes the filter.
*
* @param pFilterConfig the filter config
* @see #init init
*
* @deprecated For compatibility only, use {@link #init init} instead.
*/
public void setFilterConfig(FilterConfig pFilterConfig) {
try {
init(pFilterConfig);
}
catch (ServletException e) {
log("Error in init(), see stacktrace for details.", e);
}
}
/**
* Gets the {@code FilterConfig} for this filter.
*
* @return the {@code FilterConfig} for this filter
* @see FilterConfig
*/
public FilterConfig getFilterConfig() {
return mFilterConfig;
}
/**
* Specifies if this filter should run once per request ({@code true}),
* or for each forward/include resource ({@code false}).
* Called automatically from the {@code init}-method, with settings
* from web.xml.
*
* @param pOncePerRequest {@code true} if the filter should run only
* once per request
* @see #mOncePerRequest
*/
@InitParam
public void setOncePerRequest(boolean pOncePerRequest) {
mOncePerRequest = pOncePerRequest;
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.lang.reflect.InvocationTargetException;
/**
* Defines a generic, protocol-independent servlet.
* <p/>
* {@code GenericServlet} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style paramter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string represenation to
* most basic types, if neccessary.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java#1 $
*/
public abstract class GenericServlet extends javax.servlet.GenericServlet {
/**
* Called by the web container to indicate to a servlet that it is being
* placed into service.
* <p/>
* This implementation stores the {@code ServletConfig} object it
* receives from the servlet container for later use. When overriding this
* form of the method, call {@code super.init(config)}.
* <p/>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
*
* @param pConfig the servlet config
* @throws ServletException
*
* @see javax.servlet.GenericServlet#init
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
@Override
public void init(ServletConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("servletconfig == null");
}
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
}
super.init(pConfig);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.BeanUtil;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.lang.reflect.InvocationTargetException;
/**
* Defines a generic, HTTP specific servlet.
* <p/>
* {@code HttpServlet} has an auto-init system, that automatically invokes
* the method matching the signature {@code void setX(&lt;Type&gt;)},
* for every init-parameter {@code x}. Both camelCase and lisp-style paramter
* naming is supported, lisp-style names will be converted to camelCase.
* Parameter values are automatically converted from string represenation to
* most basic types, if neccessary.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java#1 $
*/
public abstract class HttpServlet extends javax.servlet.http.HttpServlet {
/**
* Called by the web container to indicate to a servlet that it is being
* placed into service.
* <p/>
* This implementation stores the {@code ServletConfig} object it
* receives from the servlet container for later use. When overriding this
* form of the method, call {@code super.init(config)}.
* <p/>
* This implementation will also set all configured key/value pairs, that
* have a matching setter method annotated with {@link InitParam}.
*
* @param pConfig the servlet config
* @throws ServletException if an error ouccured during init
*
* @see javax.servlet.GenericServlet#init
* @see #init() init
* @see BeanUtil#configure(Object, java.util.Map, boolean)
*/
@Override
public void init(ServletConfig pConfig) throws ServletException {
if (pConfig == null) {
throw new ServletConfigException("servletconfig == null");
}
try {
BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
}
catch (InvocationTargetException e) {
throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
}
super.init(pConfig);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import java.lang.annotation.*;
/**
* Annotation to be used by serlvets/filters, to have their init-method
* automatically convert and set values from their respective configuration.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/InitParam.java#1 $
* @see com.twelvemonkeys.servlet.GenericFilter#init(javax.servlet.FilterConfig)
* @see com.twelvemonkeys.servlet.GenericServlet#init(javax.servlet.ServletConfig)
* @see com.twelvemonkeys.servlet.HttpServlet#init(javax.servlet.ServletConfig)
*/
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InitParam {
String name() default "";
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A {@code ServletOutputStream} implementation backed by a
* {@link java.io.OutputStream}. For filters that need to buffer the
* response and do post filtering, it may be used like this:<pre>
* ByteArrayOutputStream buffer = new ByteArraOutputStream();
* ServletOutputStream adapter = new OutputStreamAdapter(buffer);
* </pre>
* <p/>
* As a {@code ServletOutputStream} is itself an {@code OutputStream}, this
* class may also be used as a superclass for wrappers of other
* {@code ServletOutputStream}s, like this:<pre>
* class FilterServletOutputStream extends OutputStreamAdapter {
* public FilterServletOutputStream(ServletOutputStream out) {
* super(out);
* }
*
* public void write(int abyte) {
* // do filtering...
* super.write(...);
* }
* }
*
* ...
*
* ServletOutputStream original = response.getOutputStream();
* ServletOutputStream wrapper = new FilterServletOutputStream(original);
* </pre>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java#1 $
*
*/
public class OutputStreamAdapter extends ServletOutputStream {
/** The wrapped {@code OutputStream}. */
protected final OutputStream mOut;
/**
* Creates an {@code OutputStreamAdapter}.
*
* @param pOut the wrapped {@code OutputStream}
*
* @throws IllegalArgumentException if {@code pOut} is {@code null}.
*/
public OutputStreamAdapter(OutputStream pOut) {
if (pOut == null) {
throw new IllegalArgumentException("out == null");
}
mOut = pOut;
}
/**
* Returns the wrapped {@code OutputStream}.
*
* @return the wrapped {@code OutputStream}.
*/
public OutputStream getOutputStream() {
return mOut;
}
public String toString() {
return "ServletOutputStream adapted from " + mOut.toString();
}
/**
* Writes a byte to the underlying stream.
*
* @param pByte the byte to write.
*
* @throws IOException if an error occurs during writing
*/
public void write(int pByte)
throws IOException {
mOut.write(pByte);
}
// Overide for efficiency
public void write(byte pBytes[])
throws IOException {
mOut.write(pBytes);
}
// Overide for efficiency
public void write(byte pBytes[], int pOff, int pLen)
throws IOException {
mOut.write(pBytes, pOff, pLen);
}
}

View File

@@ -0,0 +1,435 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Enumeration;
/**
* A simple proxy servlet implementation. Supports HTTP and HTTPS.
* <p/>
* Note: The servlet is not a true HTTP proxy as described in
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.html">RFC 2616</a>,
* instead it passes on all incoming HTTP requests to the configured remote
* server.
* Useful for bypassing firewalls or to avoid exposing internal network
* infrastructure to external clients.
* <p/>
* At the moment, no caching of content is implemented.
* <p/>
* If the {@code remoteServer} init parameter is not set, the servlet will
* respond by sending a {@code 500 Internal Server Error} response to the client.
* If the configured remote server is down, or unreachable, the servlet will
* respond by sending a {@code 502 Bad Gateway} response to the client.
* Otherwise, the response from the remote server will be tunneled unmodified
* to the client.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java#1 $
*/
public class ProxyServlet extends GenericServlet {
/** Remote server host name or IP address */
protected String mRemoteServer = null;
/** Remote server port */
protected int mRemotePort = 80;
/** Remote server "mount" path */
protected String mRemotePath = "";
private static final String HTTP_REQUEST_HEADER_HOST = "host";
private static final String HTTP_RESPONSE_HEADER_SERVER = "server";
private static final String MESSAGE_REMOTE_SERVER_NOT_CONFIGURED = "Remote server not configured.";
/**
* Called by {@code init} to set the remote server. Must be a valid host
* name or IP address. No default.
*
* @param pRemoteServer
*/
public void setRemoteServer(String pRemoteServer) {
mRemoteServer = pRemoteServer;
}
/**
* Called by {@code init} to set the remote port. Must be a number.
* Default is {@code 80}.
*
* @param pRemotePort
*/
public void setRemotePort(String pRemotePort) {
try {
mRemotePort = Integer.parseInt(pRemotePort);
}
catch (NumberFormatException e) {
log("RemotePort must be a number!", e);
}
}
/**
* Called by {@code init} to set the remote path. May be an empty string
* for the root path, or any other valid path on the remote server.
* Default is {@code ""}.
*
* @param pRemotePath
*/
public void setRemotePath(String pRemotePath) {
if (StringUtil.isEmpty(pRemotePath)) {
pRemotePath = "";
}
else if (pRemotePath.charAt(0) != '/') {
pRemotePath = "/" + pRemotePath;
}
mRemotePath = pRemotePath;
}
/**
* Override {@code service} to use HTTP specifics.
*
* @param pRequest
* @param pResponse
*
* @throws ServletException
* @throws IOException
*
* @see #service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
}
/**
* Services a single request.
* Supports HTTP and HTTPS.
*
* @param pRequest
* @param pResponse
*
* @throws ServletException
* @throws IOException
*
* @see ProxyServlet Class descrition
*/
protected void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
// Sanity check configuration
if (mRemoteServer == null) {
log(MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
pResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
return;
}
HttpURLConnection remoteConnection = null;
try {
// Recreate request URI for remote request
String requestURI = createRemoteRequestURI(pRequest);
URL remoteURL = new URL(pRequest.getScheme(), mRemoteServer, mRemotePort, requestURI);
// Get connection, with method from original request
// NOTE: The actual connection is not done before we ask for streams...
// NOTE: The HttpURLConnection is supposed to handle multiple
// requests to the same server internally
String method = pRequest.getMethod();
remoteConnection = (HttpURLConnection) remoteURL.openConnection();
remoteConnection.setRequestMethod(method);
// Copy header fields
copyHeadersFromClient(pRequest, remoteConnection);
// Do proxy specifc stuff?
// TODO: Read up the specs from RFC 2616 (HTTP) on proxy behaviour
// TODO: RFC 2616 says "[a] proxy server MUST NOT establish an HTTP/1.1
// persistent connection with an HTTP/1.0 client"
// Copy message body from client to remote server
copyBodyFromClient(pRequest, remoteConnection);
// Set response status code from remote server to client
int responseCode = remoteConnection.getResponseCode();
pResponse.setStatus(responseCode);
//System.out.println("Response is: " + responseCode + " " + remoteConnection.getResponseMessage());
// Copy header fields back
copyHeadersToClient(remoteConnection, pResponse);
// More proxy specific stuff?
// Copy message body from remote server to client
copyBodyToClient(remoteConnection, pResponse);
}
catch (ConnectException e) {
// In case we could not connecto to the remote server
log("Could not connect to remote server.", e);
pResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, e.getMessage());
}
finally {
// Disconnect from server
// TODO: Should we actually do this?
if (remoteConnection != null) {
remoteConnection.disconnect();
}
}
}
/**
* Copies the message body from the remote server to the client (outgoing
* {@code HttpServletResponse}).
*
* @param pRemoteConnection
* @param pResponse
*
* @throws IOException
*/
private void copyBodyToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) throws IOException {
InputStream fromRemote = null;
OutputStream toClient = null;
try {
// Get either input or error stream
try {
fromRemote = pRemoteConnection.getInputStream();
}
catch (IOException e) {
// If exception, use errorStream instead
fromRemote = pRemoteConnection.getErrorStream();
}
// I guess the stream might be null if there is no response other
// than headers (Continue, No Content, etc).
if (fromRemote != null) {
toClient = pResponse.getOutputStream();
FileUtil.copy(fromRemote, toClient);
}
}
finally {
if (fromRemote != null) {
try {
fromRemote.close();
}
catch (IOException e) {
log("Stream from remote could not be closed.", e);
}
}
if (toClient != null) {
try {
toClient.close();
}
catch (IOException e) {
log("Stream to client could not be closed.", e);
}
}
}
}
/**
* Copies the message body from the client (incomming
* {@code HttpServletRequest}) to the remote server if the request method
* is {@code POST} or <tt>PUT<tt>.
* Otherwise this method does nothing.
*
* @param pRequest
* @param pRemoteConnection
*
* @throws java.io.IOException
*/
private void copyBodyFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) throws IOException {
// If this is a POST or PUT, copy message body from client remote server
if (!("POST".equals(pRequest.getMethod()) || "PUT".equals(pRequest.getMethod()))) {
return;
}
// NOTE: Setting doOutput to true, will make it a POST request (why?)...
pRemoteConnection.setDoOutput(true);
// Get streams and do the copying
InputStream fromClient = null;
OutputStream toRemote = null;
try {
fromClient = pRequest.getInputStream();
toRemote = pRemoteConnection.getOutputStream();
FileUtil.copy(fromClient, toRemote);
}
finally {
if (fromClient != null) {
try {
fromClient.close();
}
catch (IOException e) {
log("Stream from client could not be closed.", e);
}
}
if (toRemote != null) {
try {
toRemote.close();
}
catch (IOException e) {
log("Stream to remote could not be closed.", e);
}
}
}
}
/**
* Creates the remote request URI based on the incoming request.
* The URI will include any query strings etc.
*
* @param pRequest
*
* @return a {@code String} representing the remote request URI
*/
private String createRemoteRequestURI(HttpServletRequest pRequest) {
StringBuilder requestURI = new StringBuilder(mRemotePath);
requestURI.append(pRequest.getPathInfo());
if (!StringUtil.isEmpty(pRequest.getQueryString())) {
requestURI.append("?");
requestURI.append(pRequest.getQueryString());
}
return requestURI.toString();
}
/**
* Copies headers from the remote connection back to the client
* (the outgoing HttpServletResponse). All headers except the "Server"
* header are copied.
*
* @param pRemoteConnection
* @param pResponse
*/
private void copyHeadersToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) {
// NOTE: There is no getHeaderFieldCount method or similar...
// Also, the getHeaderFields() method was introduced in J2SE 1.4, and
// we want to be 1.2 compatible.
// So, just try to loop until there are no more headers.
int i = 0;
while (true) {
String key = pRemoteConnection.getHeaderFieldKey(i);
// NOTE: getHeaderField(String) returns only the last value
String value = pRemoteConnection.getHeaderField(i);
// If the key is not null, life is simple, and Sun is shining
// However, the default implementations includes the HTTP response
// code ("HTTP/1.1 200 Ok" or similar) as a header field with
// key "null" (why..?)...
// In addition, we want to skip the original "Server" header
if (key != null && !HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
//System.out.println("client <<<-- remote: " + key + ": " + value);
pResponse.setHeader(key, value);
}
else if (value == null) {
// If BOTH key and value is null, there are no more header fields
break;
}
i++;
}
/* 1.4+ version below....
Map headers = pRemoteConnection.getHeaderFields();
for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();) {
Map.Entry header = (Map.Entry) iterator.next();
List values = (List) header.getValue();
for (Iterator valueIter = values.iterator(); valueIter.hasNext();) {
String value = (String) valueIter.next();
String key = (String) header.getKey();
// Skip the server header
if (HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
key = null;
}
// The default implementations includes the HTTP response code
// ("HTTP/1.1 200 Ok" or similar) as a header field with
// key "null" (why..?)...
if (key != null) {
//System.out.println("client <<<-- remote: " + key + ": " + value);
pResponse.setHeader(key, value);
}
}
}
*/
}
/**
* Copies headers from the client (the incoming {@code HttpServletRequest})
* to the outgoing connection.
* All headers except the "Host" header are copied.
*
* @param pRequest
* @param pRemoteConnection
*/
private void copyHeadersFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) {
Enumeration headerNames = pRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
Enumeration headerValues = pRequest.getHeaders(headerName);
// Skip the "host" header, as we want something else
if (HTTP_REQUEST_HEADER_HOST.equalsIgnoreCase(headerName)) {
// Skip this header
headerName = null;
}
// Set the the header to the remoteConnection
if (headerName != null) {
// Convert from multiple line to single line, comma separated, as
// there seems to be a shortcoming in the URLConneciton API...
StringBuilder headerValue = new StringBuilder();
while (headerValues.hasMoreElements()) {
String value = (String) headerValues.nextElement();
headerValue.append(value);
if (headerValues.hasMoreElements()) {
headerValue.append(", ");
}
}
//System.out.println("client -->>> remote: " + headerName + ": " + headerValue);
pRemoteConnection.setRequestProperty(headerName, headerValue.toString());
}
}
}
}

View File

@@ -0,0 +1,40 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.util.CollectionUtil;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.Iterator;
/**
* HeaderMap
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/SerlvetHeadersMapAdapter.java#1 $
*/
class SerlvetHeadersMapAdapter extends AbstractServletMapAdapter {
protected final HttpServletRequest mRequest;
public SerlvetHeadersMapAdapter(HttpServletRequest pRequest) {
if (pRequest == null) {
throw new IllegalArgumentException("request == null");
}
mRequest = pRequest;
}
protected Iterator<String> valuesImpl(String pName) {
//noinspection unchecked
Enumeration<String> headers = mRequest.getHeaders(pName);
return headers == null ? null : CollectionUtil.iterator(headers);
}
protected Iterator<String> keysImpl() {
//noinspection unchecked
Enumeration<String> headerNames = mRequest.getHeaderNames();
return headerNames == null ? null : CollectionUtil.iterator(headerNames);
}
}

View File

@@ -0,0 +1,38 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.util.CollectionUtil;
import javax.servlet.http.HttpServletRequest;
import java.util.Iterator;
import java.util.Enumeration;
/**
* HeaderMap
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/SerlvetParametersMapAdapter.java#1 $
*/
class SerlvetParametersMapAdapter extends AbstractServletMapAdapter {
protected final HttpServletRequest mRequest;
public SerlvetParametersMapAdapter(HttpServletRequest pRequest) {
if (pRequest == null) {
throw new IllegalArgumentException("request == null");
}
mRequest = pRequest;
}
protected Iterator<String> valuesImpl(String pName) {
String[] values = mRequest.getParameterValues(pName);
return values == null ? null : CollectionUtil.iterator(values);
}
protected Iterator<String> keysImpl() {
//noinspection unchecked
Enumeration<String> names = mRequest.getParameterNames();
return names == null ? null : CollectionUtil.iterator(names);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletException;
/**
* ServletConfigException.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java#2 $
*/
public class ServletConfigException extends ServletException {
/**
* Creates a {@code ServletConfigException} with the given message.
*
* @param pMessage the exception message
*/
public ServletConfigException(String pMessage) {
super(pMessage);
}
/**
* Creates a {@code ServletConfigException} with the given message and cause.
*
* @param pMessage the exception message
* @param pCause the exception cause
*/
public ServletConfigException(String pMessage, Throwable pCause) {
super(pMessage, pCause);
if (getCause() == null) {
initCause(pCause);
}
}
/**
* Creates a {@code ServletConfigException} with the cause.
*
* @param pCause the exception cause
*/
public ServletConfigException(Throwable pCause) {
super("Erorr in Servlet configuration: " + pCause.getMessage(), pCause);
if (getCause() == null) {
initCause(pCause);
}
}
/**
* Gets the cause of this {@code ServletConfigException}.
*
* @return the cause, or {@code null} if unknown.
* @see #getRootCause()
*/
// public final Throwable getCause() {
// Throwable cause = super.getCause();
// return cause != null ? cause : super.getRootCause();
// }
/**
* @deprecated Use {@link #getCause()} instead.
*/
// public final Throwable getRootCause() {
// return getCause();
// }
}

View File

@@ -0,0 +1,284 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import java.io.Serializable;
import java.util.*;
/**
* {@code ServletConfig} or {@code FilterConfig} adapter, that implements
* the {@code Map} interface for interoperability with collection-based API's.
* <p/>
* This {@code Map} is not synchronized.
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java#2 $
*/
class ServletConfigMapAdapter extends AbstractMap<String, String> implements Map<String, String>, Serializable, Cloneable {
enum ConfigType {
ServletConfig, FilterConfig, ServletContext
}
// private final boolean mIsServlet;
private final ConfigType mType;
private final ServletConfig mServletConfig;
private final FilterConfig mFilterConfig;
private final ServletContext mServletContext;
// Cache the entry set
private transient Set<Entry<String, String>> mEntrySet;
public ServletConfigMapAdapter(ServletConfig pConfig) {
this(pConfig, ConfigType.ServletConfig);
}
public ServletConfigMapAdapter(FilterConfig pConfig) {
this(pConfig, ConfigType.FilterConfig);
}
public ServletConfigMapAdapter(ServletContext pContext) {
this(pContext, ConfigType.ServletContext);
}
private ServletConfigMapAdapter(Object pConfig, ConfigType pType) {
if (pConfig == null) {
// Could happen of client code invokes with null reference
throw new IllegalArgumentException("Config == null");
}
mType = pType;
switch (mType) {
case ServletConfig:
mServletConfig = (ServletConfig) pConfig;
mFilterConfig = null;
mServletContext = null;
break;
case FilterConfig:
mServletConfig = null;
mFilterConfig = (FilterConfig) pConfig;
mServletContext = null;
break;
case ServletContext:
mServletConfig = null;
mFilterConfig = null;
mServletContext = (ServletContext) pConfig;
break;
default:
throw new IllegalArgumentException("Wrong type: " + pType);
}
}
/**
* Gets the servlet or filter name from the config.
*
* @return the servlet or filter name
*/
public final String getName() {
switch (mType) {
case ServletConfig:
return mServletConfig.getServletName();
case FilterConfig:
return mFilterConfig.getFilterName();
case ServletContext:
return mServletContext.getServletContextName();
default:
throw new IllegalStateException();
}
}
/**
* Gets the servlet context from the config.
*
* @return the servlet context
*/
public final ServletContext getServletContext() {
switch (mType) {
case ServletConfig:
return mServletConfig.getServletContext();
case FilterConfig:
return mFilterConfig.getServletContext();
case ServletContext:
return mServletContext;
default:
throw new IllegalStateException();
}
}
public final Enumeration getInitParameterNames() {
switch (mType) {
case ServletConfig:
return mServletConfig.getInitParameterNames();
case FilterConfig:
return mFilterConfig.getInitParameterNames();
case ServletContext:
return mServletContext.getInitParameterNames();
default:
throw new IllegalStateException();
}
}
public final String getInitParameter(final String pName) {
switch (mType) {
case ServletConfig:
return mServletConfig.getInitParameter(pName);
case FilterConfig:
return mFilterConfig.getInitParameter(pName);
case ServletContext:
return mServletContext.getInitParameter(pName);
default:
throw new IllegalStateException();
}
}
public Set<Entry<String, String>> entrySet() {
if (mEntrySet == null) {
mEntrySet = createEntrySet();
}
return mEntrySet;
}
private Set<Entry<String, String>> createEntrySet() {
return new AbstractSet<Entry<String, String>>() {
// Cache size, if requested, -1 means not calculated
private int mSize = -1;
public Iterator<Entry<String, String>> iterator() {
return new Iterator<Entry<String, String>>() {
// Iterator is backed by initParameterNames enumeration
final Enumeration mNames = getInitParameterNames();
public boolean hasNext() {
return mNames.hasMoreElements();
}
public Entry<String, String> next() {
final String key = (String) mNames.nextElement();
return new Entry<String, String>() {
public String getKey() {
return key;
}
public String getValue() {
return get(key);
}
public String setValue(String pValue) {
throw new UnsupportedOperationException();
}
// NOTE: Override equals
public boolean equals(Object pOther) {
if (!(pOther instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) pOther;
Object value = get(key);
Object rKey = e.getKey();
Object rValue = e.getValue();
return (key == null ? rKey == null : key.equals(rKey))
&& (value == null ? rValue == null : value.equals(rValue));
}
// NOTE: Override hashCode to keep the map's
// hashCode constant and compatible
public int hashCode() {
Object value = get(key);
return ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + get(key);
}
};
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public int size() {
if (mSize < 0) {
mSize = calculateSize();
}
return mSize;
}
private int calculateSize() {
final Enumeration names = getInitParameterNames();
int size = 0;
while (names.hasMoreElements()) {
size++;
names.nextElement();
}
return size;
}
};
}
public String get(Object pKey) {
return getInitParameter(StringUtil.valueOf(pKey));
}
/// Unsupported Map methods
@Override
public String put(String pKey, String pValue) {
throw new UnsupportedOperationException();
}
@Override
public String remove(Object pKey) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map pMap) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.OutputStream;
/**
* A delegate for handling stream support in wrapped servlet responses.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java#2 $
*/
public class ServletResponseStreamDelegate {
private Object mOut = null;
protected final ServletResponse mResponse;
public ServletResponseStreamDelegate(ServletResponse pResponse) {
if (pResponse == null) {
throw new IllegalArgumentException("response == null");
}
mResponse = pResponse;
}
// NOTE: Intentionally NOT threadsafe, as one request/response should be
// handled by one thread ONLY.
public final ServletOutputStream getOutputStream() throws IOException {
if (mOut == null) {
OutputStream out = createOutputStream();
mOut = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out);
}
else if (mOut instanceof PrintWriter) {
throw new IllegalStateException("getWriter() allready called.");
}
return (ServletOutputStream) mOut;
}
// NOTE: Intentionally NOT threadsafe, as one request/response should be
// handled by one thread ONLY.
public final PrintWriter getWriter() throws IOException {
if (mOut == null) {
// NOTE: getCharacterEncoding may should not return null
OutputStream out = createOutputStream();
String charEncoding = mResponse.getCharacterEncoding();
mOut = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out));
}
else if (mOut instanceof ServletOutputStream) {
throw new IllegalStateException("getOutputStream() allready called.");
}
return (PrintWriter) mOut;
}
/**
* Returns the {@code OutputStream}.
* Override this method to provide a decoreated outputstream.
* This method is guaranteed to be invoked only once for a request/response.
* <P/>
* This implementation simply returns the output stream from the wrapped
* response.
*
* @return the {@code OutputStream} to use for the response
* @throws IOException if an I/O exception occurs
*/
protected OutputStream createOutputStream() throws IOException {
return mResponse.getOutputStream();
}
public void flushBuffer() throws IOException {
if (mOut instanceof ServletOutputStream) {
((ServletOutputStream) mOut).flush();
}
else if (mOut != null) {
((PrintWriter) mOut).flush();
}
}
public void resetBuffer() {
// TODO: Is this okay? Probably not... :-)
mOut = null;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,311 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ThrottleFilter, a filter for easing server during heavy load.
* <!--
* Renamed from LoadShutoffFilter...
* Happened to be listening to Xploding Plastix' Shakedown Shutoff at the time..
* -->
* Intercepts requests, and returns HTTP response code 503
* (Service Unavailable), if there are more than a given number of concurrent
* requests, to avoid large backlogs. The number of concurrent requests and the
* response messages sent to the user agent, is configurable from the web
* descriptor.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java#1 $
* @see #setMaxConcurrentThreadCount
* @see #setResponseMessages
*/
public class ThrottleFilter extends GenericFilter {
/**
* Minimum free thread count, defaults to {@code 10}
*/
protected int mMaxConcurrentThreadCount = 10;
/**
* The number of running request threads
*/
private int mRunningThreads = 0;
private final Object mRunningThreadsLock = new Object();
/**
* Default response message sent to user agents, if the request is rejected
*/
protected final static String DEFUALT_RESPONSE_MESSAGE =
"Service temporarily unavailable, please try again later.";
/**
* Default response content type
*/
protected static final String DEFAULT_TYPE = "text/html";
/**
* The reposne message sent to user agenta, if the request is rejected
*/
private Map mResponseMessageNames = new HashMap(10);
/**
* The reposne message sent to user agents, if the request is rejected
*/
private String[] mResponseMessageTypes = null;
/**
* Cache for response messages
*/
private Map mResponseCache = new HashMap(10);
/**
* Sets the minimum free thread count.
*
* @param pMaxConcurrentThreadCount
*/
public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) {
if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) {
try {
mMaxConcurrentThreadCount = Integer.parseInt(pMaxConcurrentThreadCount);
}
catch (NumberFormatException nfe) {
// Use default
}
}
}
/**
* Sets the response message sent to the user agent, if the request is
* rejected.
* <BR/>
* The format is {@code &lt;mime-type&gt;=&lt;filename&gt;,
* &lt;mime-type&gt;=&lt;filename&gt;}.
* <BR/>
* Example: {@code &lt;text/vnd.wap.wmlgt;=&lt;/errors/503.wml&gt;,
* &lt;text/html&gt;=&lt;/errors/503.html&gt;}
*
* @param pResponseMessages
*/
public void setResponseMessages(String pResponseMessages) {
// Split string in type=filename pairs
String[] mappings = StringUtil.toStringArray(pResponseMessages, ", \r\n\t");
List types = new ArrayList();
for (int i = 0; i < mappings.length; i++) {
// Split pairs on '='
String[] mapping = StringUtil.toStringArray(mappings[i], "= ");
// Test for wrong mapping
if ((mapping == null) || (mapping.length < 2)) {
log("Error in init param \"responseMessages\": " + pResponseMessages);
continue;
}
types.add(mapping[0]);
mResponseMessageNames.put(mapping[0], mapping[1]);
}
// Create arrays
mResponseMessageTypes = (String[]) types.toArray(new String[types.size()]);
}
/**
* @param pRequest
* @param pResponse
* @param pChain
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
try {
if (beginRequest()) {
// Continue request
pChain.doFilter(pRequest, pResponse);
}
else {
// Send error and end request
// Get HTTP specific versions
HttpServletRequest request = (HttpServletRequest) pRequest;
HttpServletResponse response = (HttpServletResponse) pResponse;
// Get content type
String contentType = getContentType(request);
// Note: This is not the way the spec says you should do it.
// However, we handle error response this way for preformace reasons.
// The "correct" way would be to use sendError() and register a servlet
// that does the content negotiation as errorpage in the web descriptor.
response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
response.setContentType(contentType);
response.getWriter().println(getMessage(contentType));
// Log warning, as this shouldn't happen too often
log("Request denied, no more available threads for requestURI=" + request.getRequestURI());
}
}
finally {
doneRequest();
}
}
/**
* Marks the beginning of a request
*
* @return <CODE>true<CODE> if the request should be handled.
*/
private boolean beginRequest() {
synchronized (mRunningThreadsLock) {
mRunningThreads++;
}
return (mRunningThreads <= mMaxConcurrentThreadCount);
}
/**
* Marks the end of the request
*/
private void doneRequest() {
synchronized (mRunningThreadsLock) {
mRunningThreads--;
}
}
/**
* Gets the content type for the response, suitable for the requesting user agent.
*
* @param pRequest
* @return the content type
*/
private String getContentType(HttpServletRequest pRequest) {
if (mResponseMessageTypes != null) {
String accept = pRequest.getHeader("Accept");
for (int i = 0; i < mResponseMessageTypes.length; i++) {
String type = mResponseMessageTypes[i];
// Note: This is not 100% correct way of doing content negotiation
// But we just want a compatible result, quick, so this is okay
if (StringUtil.contains(accept, type)) {
return type;
}
}
}
// If none found, return default
return DEFAULT_TYPE;
}
/**
* Gets the response message for the given content type.
*
* @param pContentType
* @return the message
*/
private String getMessage(String pContentType) {
String fileName = (String) mResponseMessageNames.get(pContentType);
// Get cached value
CacheEntry entry = (CacheEntry) mResponseCache.get(fileName);
if ((entry == null) || entry.isExpired()) {
// Create and add or replace cached value
entry = new CacheEntry(readMessage(fileName));
mResponseCache.put(fileName, entry);
}
// Return value
return (entry.getValue() != null)
? (String) entry.getValue()
: DEFUALT_RESPONSE_MESSAGE;
}
/**
* Reads the response message from a file in the current web app.
*
* @param pFileName
* @return the message
*/
private String readMessage(String pFileName) {
try {
// Read resource from web app
InputStream is = getServletContext().getResourceAsStream(pFileName);
if (is != null) {
return new String(FileUtil.read(is));
}
else {
log("File not found: " + pFileName);
}
}
catch (IOException ioe) {
log("Error reading file: " + pFileName + " (" + ioe.getMessage() + ")");
}
return null;
}
/**
* Keeps track of Cached objects
*/
private static class CacheEntry {
private Object mValue;
private long mTimestamp = -1;
CacheEntry(Object pValue) {
mValue = pValue;
mTimestamp = System.currentTimeMillis();
}
Object getValue() {
return mValue;
}
boolean isExpired() {
return (System.currentTimeMillis() - mTimestamp) > 60000; // Cache 1 minute
}
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* TimingFilter class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java#1 $
*/
public class TimingFilter extends GenericFilter {
private String mAttribUsage = null;
/**
* Method init
*
* @throws ServletException
*/
public void init() throws ServletException {
mAttribUsage = getFilterName() + ".timerDelta";
}
/**
*
* @param pRequest
* @param pResponse
* @param pChain
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
// Get total usage of earlier filters on same level
Object usageAttrib = pRequest.getAttribute(mAttribUsage);
long total = 0;
if (usageAttrib instanceof Long) {
// If set, get value, and remove attribute for nested resources
total = ((Long) usageAttrib).longValue();
pRequest.removeAttribute(mAttribUsage);
}
// Start timing
long start = System.currentTimeMillis();
try {
// Continue chain
pChain.doFilter(pRequest, pResponse);
}
finally {
// Stop timing
long end = System.currentTimeMillis();
// Get time usage of included resources, add to total usage
usageAttrib = pRequest.getAttribute(mAttribUsage);
long usage = 0;
if (usageAttrib instanceof Long) {
usage = ((Long) usageAttrib).longValue();
}
// Get the name of the included resource
String resourceURI = ServletUtil.getIncludeRequestURI(pRequest);
// If none, this is probably the parent page itself
if (resourceURI == null) {
resourceURI = ((HttpServletRequest) pRequest).getRequestURI();
}
long delta = end - start;
log("Request processing time for resource \"" + resourceURI + "\": " +
(delta - usage) + " ms (accumulated: " + delta + " ms).");
// Store total usage
total += delta;
pRequest.setAttribute(mAttribUsage, new Long(total));
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.OutputStream;
import java.io.FilterOutputStream;
/**
* Removes extra unneccessary white space from a servlet response.
* White space is defined as per {@link Character#isWhitespace(char)}.
* <p/>
* This filter has no understanding of the content in the reponse, and will
* remove repeated white space anywhere in the stream. It is intended for
* removing white space from HTML or XML streams, but this limitation makes it
* less suited for filtering HTML/XHTML with embedded CSS or JavaScript,
* in case white space should be significant here. It is strongly reccommended
* you keep CSS and JavaScript in separate files (this will have the added
* benefit of further reducing the ammount of data communicated between
* server and client).
* <p/>
* <em>At the moment this filter has no concept of encoding</em>.
* This means, that if some multi-byte escape sequence contains one or more
* bytes that <em>individually</em> is treated as a white space, these bytes
* may be skipped.
* As <a href="http://en.wikipedia.org/wiki/UTF-8" title="UTF-8">UTF-8</a>
* guarantees that no bytes are repeated in this way, this filter can safely
* filter UTF-8.
* Simple 8 bit character encodings, like the
* <a href="http://en.wikipedia.org/wiki/ISO/IEC_8859"
* title="ISO/IEC 8859">ISO/IEC 8859</a> standard, or
* <a href="http://en.wikipedia.org/wiki/Windows-1252" title="Windows-1252">
* are always safe.
* <p/>
* <b>Configuration</b><br/>
* To use {@code TrimWhiteSpaceFilter} in your web-application, you simply need
* to add it to your web descriptor ({@code web.xml}).
* If using a servlet container that supports the Servlet 2.4 spec, the new
* {@code dispatcher} element should be used, and set to
* {@code REQUEST/FORWARD}, to make sure the filter is invoked only once for
* requests.
* If using an older web descriptor, set the {@code init-param}
* {@code "once-per-request"} to {@code "true"} (this will have the same effect,
* but might perform slightly worse than the 2.4 version).
* Please see the examples below.
* <p/>
* <b>Servlet 2.4 version, filter section:</b><br/>
* <pre>
* &lt;!-- TrimWS Filter Configuration --&gt;
* &lt;filter&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;filter-class&gt;com.twelvemonkeys.servlet.TrimWhiteSpaceFilter&lt;/filter-class&gt;
* &lt;!-- auto-flush=true is the default, may be omitted --&gt;
* &lt;init-param&gt;
* &lt;param-name&gt;auto-flush&lt;/param-name&gt;
* &lt;param-value&gt;true&lt;/param-value&gt;
* &lt;/init-param&gt;
* &lt;/filter&gt;
* </pre>
* <b>Filter-mapping section:</b><br/>
* <pre>
* &lt;!-- TimWS Filter Mapping --&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;trimws&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* </pre>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java#2 $
*/
public class TrimWhiteSpaceFilter extends GenericFilter {
private boolean mAutoFlush = true;
@InitParam
public void setAutoFlush(final boolean pAutoFlush) {
mAutoFlush = pAutoFlush;
}
public void init() throws ServletException {
super.init();
log("Automatic flushing is " + (mAutoFlush ? "enabled" : "disabled"));
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
ServletResponseWrapper wrapped = new TrimWSServletResponseWrapper(pResponse);
pChain.doFilter(pRequest, ServletUtil.createWrapper(wrapped));
if (mAutoFlush) {
wrapped.flushBuffer();
}
}
static final class TrimWSFilterOutputStream extends FilterOutputStream {
boolean mLastWasWS = true; // Avoids leading WS by init to true
public TrimWSFilterOutputStream(OutputStream pOut) {
super(pOut);
}
// Override this, in case the wrapped outputstream overrides...
public final void write(byte pBytes[]) throws IOException {
write(pBytes, 0, pBytes.length);
}
// Override this, in case the wrapped outputstream overrides...
public final void write(byte pBytes[], int pOff, int pLen) throws IOException {
if (pBytes == null) {
throw new NullPointerException("bytes == null");
}
else if (pOff < 0 || pLen < 0 || (pOff + pLen > pBytes.length)) {
throw new IndexOutOfBoundsException("Bytes: " + pBytes.length + " Offset: " + pOff + " Length: " + pLen);
}
for (int i = 0; i < pLen ; i++) {
write(pBytes[pOff + i]);
}
}
public void write(int pByte) throws IOException {
// TODO: Is this good enough for multi-byte encodings like UTF-16?
// Consider writing through a Writer that does that for us, and
// also buffer whitespace, so we write a linefeed every time there's
// one in the original...
// According to http://en.wikipedia.org/wiki/UTF-8:
// "[...] US-ASCII octet values do not appear otherwise in a UTF-8
// encoded character stream. This provides compatibility with file
// systems or other software (e.g., the printf() function in
// C libraries) that parse based on US-ASCII values but are
// transparent to other values."
if (!Character.isWhitespace((char) pByte)) {
// If char is not WS, just store
super.write(pByte);
mLastWasWS = false;
}
else {
// TODO: Consider writing only 0x0a (LF) and 0x20 (space)
// Else, if char is WS, store first, skip the rest
if (!mLastWasWS) {
if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a
super.write(0x0a);
}
else {
super.write(pByte);
}
}
mLastWasWS = true;
}
}
}
private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate {
public TrimWSStreamDelegate(ServletResponse pResponse) {
super(pResponse);
}
protected OutputStream createOutputStream() throws IOException {
return new TrimWSFilterOutputStream(mResponse.getOutputStream());
}
}
static class TrimWSServletResponseWrapper extends ServletResponseWrapper {
private final ServletResponseStreamDelegate mStreamDelegate = new TrimWSStreamDelegate(getResponse());
public TrimWSServletResponseWrapper(ServletResponse pResponse) {
super(pResponse);
}
public ServletOutputStream getOutputStream() throws IOException {
return mStreamDelegate.getOutputStream();
}
public PrintWriter getWriter() throws IOException {
return mStreamDelegate.getWriter();
}
public void setContentLength(int pLength) {
// Will be changed by filter, so don't set.
}
@Override
public void flushBuffer() throws IOException {
mStreamDelegate.flushBuffer();
}
@Override
public void resetBuffer() {
mStreamDelegate.resetBuffer();
}
// TODO: Consider picking up content-type/encoding, as we can only
// filter US-ASCII, UTF-8 and other compatible encodings?
}
}

View File

@@ -0,0 +1,47 @@
package com.twelvemonkeys.servlet.cache;
import java.io.File;
import java.net.URI;
/**
* AbstractCacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/AbstractCacheRequest.java#1 $
*/
public abstract class AbstractCacheRequest implements CacheRequest {
private final URI mRequestURI;
private final String mMethod;
protected AbstractCacheRequest(final URI pRequestURI, final String pMethod) {
if (pRequestURI == null) {
throw new IllegalArgumentException("request URI == null");
}
if (pMethod == null) {
throw new IllegalArgumentException("method == null");
}
mRequestURI = pRequestURI;
mMethod = pMethod;
}
public URI getRequestURI() {
return mRequestURI;
}
public String getMethod() {
return mMethod;
}
// TODO: Consider overriding equals/hashcode
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append("[URI=").append(mRequestURI)
.append(", parameters=").append(getParameters())
.append(", headers=").append(getHeaders())
.append("]").toString();
}
}

View File

@@ -0,0 +1,45 @@
package com.twelvemonkeys.servlet.cache;
import java.util.*;
/**
* AbstractCacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/AbstractCacheResponse.java#1 $
*/
public abstract class AbstractCacheResponse implements CacheResponse {
private int mStatus;
private final Map<String, List<String>> mHeaders = new LinkedHashMap<String, List<String>>(); // Insertion order
private final Map<String, List<String>> mReadableHeaders = Collections.unmodifiableMap(mHeaders);
public int getStatus() {
return mStatus;
}
public void setStatus(int pStatusCode) {
mStatus = pStatusCode;
}
public void addHeader(String pHeaderName, String pHeaderValue) {
setHeader(pHeaderName, pHeaderValue, true);
}
public void setHeader(String pHeaderName, String pHeaderValue) {
setHeader(pHeaderName, pHeaderValue, false);
}
private void setHeader(String pHeaderName, String pHeaderValue, boolean pAdd) {
List<String> values = pAdd ? mHeaders.get(pHeaderName) : null;
if (values == null) {
values = new ArrayList<String>();
mHeaders.put(pHeaderName, values);
}
values.add(pHeaderValue);
}
public Map<String, List<String>> getHeaders() {
return mReadableHeaders;
}
}

View File

@@ -0,0 +1,14 @@
package com.twelvemonkeys.servlet.cache;
/**
* CacheException
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheException.java#1 $
*/
public class CacheException extends Exception {
public CacheException(Throwable pCause) {
super(pCause);
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.GenericFilter;
import com.twelvemonkeys.servlet.ServletConfigException;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A Filter that provides response caching, for HTTP {@code GET} requests.
* <p/>
* Originally based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java#4 $
*
*/
public class CacheFilter extends GenericFilter {
HTTPCache mCache;
/**
* Initializes the filter
*
* @throws javax.servlet.ServletException
*/
public void init() throws ServletException {
FilterConfig config = getFilterConfig();
// Default don't delete cache files on exit (peristent cache)
boolean deleteCacheOnExit = "TRUE".equalsIgnoreCase(config.getInitParameter("deleteCacheOnExit"));
// Default expiry time 10 minutes
int expiryTime = 10 * 60 * 1000;
String expiryTimeStr = config.getInitParameter("expiryTime");
if (!StringUtil.isEmpty(expiryTimeStr)) {
try {
expiryTime = Integer.parseInt(expiryTimeStr);
}
catch (NumberFormatException e) {
throw new ServletConfigException("Could not parse expiryTime: " + e.toString(), e);
}
}
// Default max mem cache size 10 MB
int memCacheSize = 10;
String memCacheSizeStr = config.getInitParameter("memCacheSize");
if (!StringUtil.isEmpty(memCacheSizeStr)) {
try {
memCacheSize = Integer.parseInt(memCacheSizeStr);
}
catch (NumberFormatException e) {
throw new ServletConfigException("Could not parse memCacheSize: " + e.toString(), e);
}
}
int maxCachedEntites = 10000;
try {
mCache = new HTTPCache(
getTempFolder(),
expiryTime,
memCacheSize * 1024 * 1024,
maxCachedEntites,
deleteCacheOnExit,
new ServletContextLoggerAdapter(getFilterName(), getServletContext())
) {
@Override
protected File getRealFile(CacheRequest pRequest) {
String contextRelativeURI = ServletUtil.getContextRelativeURI(((ServletCacheRequest) pRequest).getRequest());
String path = getServletContext().getRealPath(contextRelativeURI);
if (path != null) {
return new File(path);
}
return null;
}
};
log("Created cache: " + mCache);
}
catch (IllegalArgumentException e) {
throw new ServletConfigException("Could not create cache: " + e.toString(), e);
}
}
private File getTempFolder() {
File tempRoot = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
if (tempRoot == null) {
throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
}
return new File(tempRoot, getFilterName());
}
public void destroy() {
log("Destroying cache: " + mCache);
mCache = null;
super.destroy();
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
// We can only cache HTTP GET/HEAD requests
if (!(pRequest instanceof HttpServletRequest
&& pResponse instanceof HttpServletResponse
&& isCachable((HttpServletRequest) pRequest))) {
pChain.doFilter(pRequest, pResponse); // Continue chain
}
else {
ServletCacheRequest cacheRequest = new ServletCacheRequest((HttpServletRequest) pRequest);
ServletCacheResponse cacheResponse = new ServletCacheResponse((HttpServletResponse) pResponse);
ServletResponseResolver resolver = new ServletResponseResolver(cacheRequest, cacheResponse, pChain);
// Render fast
try {
mCache.doCached(cacheRequest, cacheResponse, resolver);
}
catch (CacheException e) {
if (e.getCause() instanceof ServletException) {
throw (ServletException) e.getCause();
}
else {
throw new ServletException(e);
}
}
finally {
pResponse.flushBuffer();
}
}
}
private boolean isCachable(HttpServletRequest pRequest) {
// TODO: Get Cache-Control: no-cache/max-age=0 and Pragma: no-cache from REQUEST too?
return "GET".equals(pRequest.getMethod()) || "HEAD".equals(pRequest.getMethod());
}
// TODO: Extract, complete and document this class, might be useful in other cases
// Maybe add it to the ServletUtil class
static class ServletContextLoggerAdapter extends Logger {
private final ServletContext mContext;
public ServletContextLoggerAdapter(String pName, ServletContext pContext) {
super(pName, null);
mContext = pContext;
}
@Override
public void log(Level pLevel, String pMessage) {
mContext.log(pMessage);
}
@Override
public void log(Level pLevel, String pMessage, Throwable pThrowable) {
mContext.log(pMessage, pThrowable);
}
}
}

View File

@@ -0,0 +1,26 @@
package com.twelvemonkeys.servlet.cache;
import java.net.URI;
import java.util.List;
import java.util.Map;
/**
* CacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheRequest.java#1 $
*/
public interface CacheRequest {
URI getRequestURI();
String getMethod();
Map<String, List<String>> getHeaders();
Map<String, List<String>> getParameters();
String getServerName();
int getServerPort();
}

View File

@@ -0,0 +1,27 @@
package com.twelvemonkeys.servlet.cache;
import java.io.OutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* CacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponse.java#1 $
*/
public interface CacheResponse {
OutputStream getOutputStream() throws IOException;
void setStatus(int pStatusCode);
int getStatus();
void addHeader(String pHeaderName, String pHeaderValue);
void setHeader(String pHeaderName, String pHeaderValue);
Map<String, List<String>> getHeaders();
}

View File

@@ -0,0 +1,261 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.net.NetUtil;
import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
/**
* CacheResponseWrapper class description.
* <p/>
* Based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java#3 $
*/
class CacheResponseWrapper extends HttpServletResponseWrapper {
private ServletResponseStreamDelegate mStreamDelegate;
private CacheResponse mResponse;
private CachedEntity mCached;
private WritableCachedResponse mCachedResponse;
private Boolean mCachable;
private int mStatus;
public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) {
super(pResponse.getResponse());
mResponse = pResponse;
mCached = pCached;
init();
}
/*
NOTE: This class defers determining if a response is cachable until the
output stream is needed.
This it the reason for the somewhat complicated logic in the add/setHeader
methods below.
*/
private void init() {
mCachable = null;
mStatus = SC_OK;
mCachedResponse = mCached.createCachedResponse();
mStreamDelegate = new ServletResponseStreamDelegate(this) {
protected OutputStream createOutputStream() throws IOException {
// Test if this request is really cachable, otherwise,
// just write through to underlying response, and don't cache
if (isCachable()) {
return mCachedResponse.getOutputStream();
}
else {
mCachedResponse.setStatus(mStatus);
mCachedResponse.writeHeadersTo(CacheResponseWrapper.this.mResponse);
return super.getOutputStream();
}
}
};
}
CachedResponse getCachedResponse() {
return mCachedResponse.getCachedResponse();
}
public boolean isCachable() {
// NOTE: Intentionally not synchronized
if (mCachable == null) {
mCachable = isCachableImpl();
}
return mCachable;
}
private boolean isCachableImpl() {
if (mStatus != SC_OK) {
return false;
}
// Vary: *
String[] values = mCachedResponse.getHeaderValues(HTTPCache.HEADER_VARY);
if (values != null) {
for (String value : values) {
if ("*".equals(value)) {
return false;
}
}
}
// Cache-Control: no-cache, no-store, must-revalidate
values = mCachedResponse.getHeaderValues(HTTPCache.HEADER_CACHE_CONTROL);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")
|| StringUtil.contains(value, "no-store")
|| StringUtil.contains(value, "must-revalidate")) {
return false;
}
}
}
// Pragma: no-cache
values = mCachedResponse.getHeaderValues(HTTPCache.HEADER_PRAGMA);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")) {
return false;
}
}
}
return true;
}
public void flushBuffer() throws IOException {
mStreamDelegate.flushBuffer();
}
public void resetBuffer() {
// Servlet 2.3
mStreamDelegate.resetBuffer();
}
public void reset() {
if (Boolean.FALSE.equals(mCachable)) {
super.reset();
}
// No else, might be cachable after all..
init();
}
public ServletOutputStream getOutputStream() throws IOException {
return mStreamDelegate.getOutputStream();
}
public PrintWriter getWriter() throws IOException {
return mStreamDelegate.getWriter();
}
public boolean containsHeader(String name) {
return mCachedResponse.getHeaderValues(name) != null;
}
public void sendError(int pStatusCode, String msg) throws IOException {
// NOT cachable
mStatus = pStatusCode;
super.sendError(pStatusCode, msg);
}
public void sendError(int pStatusCode) throws IOException {
// NOT cachable
mStatus = pStatusCode;
super.sendError(pStatusCode);
}
public void setStatus(int pStatusCode, String sm) {
// NOTE: This method is deprecated
setStatus(pStatusCode);
}
public void setStatus(int pStatusCode) {
// NOT cachable unless pStatusCode == 200 (or a FEW others?)
if (pStatusCode != SC_OK) {
mStatus = pStatusCode;
super.setStatus(pStatusCode);
}
}
public void sendRedirect(String pLocation) throws IOException {
// NOT cachable
mStatus = SC_MOVED_TEMPORARILY;
super.sendRedirect(pLocation);
}
public void setDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setDateHeader(pName, pValue);
}
mCachedResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue));
}
public void addDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addDateHeader(pName, pValue);
}
mCachedResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue));
}
public void setHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setHeader(pName, pValue);
}
mCachedResponse.setHeader(pName, pValue);
}
public void addHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addHeader(pName, pValue);
}
mCachedResponse.addHeader(pName, pValue);
}
public void setIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setIntHeader(pName, pValue);
}
mCachedResponse.setHeader(pName, String.valueOf(pValue));
}
public void addIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addIntHeader(pName, pValue);
}
mCachedResponse.addHeader(pName, String.valueOf(pValue));
}
public final void setContentType(String type) {
setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
/**
* CachedEntity
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java#3 $
*/
interface CachedEntity {
/**
* Renders the cached entity to the response.
*
* @param pRequest the request
* @param pResponse the response
* @throws java.io.IOException if an I/O exception occurs
*/
void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException;
/**
* Captures (caches) the response for the given request.
*
* @param pRequest the request
* @param pResponse the response
* @throws java.io.IOException if an I/O exception occurs
*
* @see #createCachedResponse()
*/
void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException;
/**
* Tests if the content of this entity is stale for the given request.
*
* @param pRequest the request
* @return {@code true} if content is stale
*/
boolean isStale(CacheRequest pRequest);
/**
* Creates a {@code WritableCachedResponse} to use to capture the response.
*
* @return a {@code WritableCachedResponse}
*/
WritableCachedResponse createCachedResponse();
}

View File

@@ -0,0 +1,172 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* CachedEntity
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java#3 $
*/
class CachedEntityImpl implements CachedEntity {
private String mCacheURI;
private HTTPCache mCache;
CachedEntityImpl(String pCacheURI, HTTPCache pCache) {
if (pCacheURI == null) {
throw new IllegalArgumentException("cacheURI == null");
}
mCacheURI = pCacheURI;
mCache = pCache;
}
public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException {
// Get cached content
CachedResponse cached = mCache.getContent(mCacheURI, pRequest);
// Sanity check
if (cached == null) {
throw new IllegalStateException("Tried to render non-cached response (cache == null).");
}
// If the cached entity is not modified since the date of the browsers
// version, then simply send a "304 Not Modified" response
// Otherwise send the full response.
// TODO: WHY DID I COMMENT OUT THIS LINE AND REPLACE IT WITH THE ONE BELOW??
//long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_LAST_MODIFIED));
long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_CACHED_TIME));
// TODO: Consider handling time skews between server "now" and client "now"?
// NOTE: The If-Modified-Since is probably right according to the server
// even in a time skew situation, as the client should use either the
// Date or Last-Modifed dates from the response headers (server generated)
long ifModifiedSince = -1L;
try {
List<String> ifmh = pRequest.getHeaders().get(HTTPCache.HEADER_IF_MODIFIED_SINCE);
ifModifiedSince = ifmh != null ? HTTPCache.getDateHeader(ifmh.get(0)) : -1L;
if (ifModifiedSince != -1L) {
/*
long serverTime = DateUtil.currentTimeMinute();
long clientTime = DateUtil.roundToMinute(pRequest.getDateHeader(HTTPCache.HEADER_DATE));
// Test if time skew is greater than time skew threshold (currently 1 minute)
if (Math.abs(serverTime - clientTime) > 1) {
// TODO: Correct error in ifModifiedSince?
}
*/
// System.out.println(" << CachedEntity >> If-Modified-Since present: " + ifModifiedSince + " --> " + NetUtil.formatHTTPDate(ifModifiedSince) + "==" + pRequest.getHeader(HTTPCache.HEADER_IF_MODIFIED_SINCE));
// System.out.println(" << CachedEntity >> Last-Modified for entity: " + lastModified + " --> " + NetUtil.formatHTTPDate(lastModified));
}
}
catch (IllegalArgumentException e) {
// Seems to be a bug in FireFox 1.0.2..?!
mCache.log("Error in date header from user-agent. User-Agent: " + pRequest.getHeaders().get("User-Agent"), e);
}
if (lastModified == -1L || (ifModifiedSince < (lastModified / 1000L) * 1000L)) {
pResponse.setStatus(cached.getStatus());
cached.writeHeadersTo(pResponse);
if (isStale(pRequest)) {
// Add warning header
// Warning: 110 <server>:<port> Content is stale
pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
}
// NOTE: At the moment we only ever try to cache HEAD and GET requests
if (!"HEAD".equals(pRequest.getMethod())) {
cached.writeContentsTo(pResponse.getOutputStream());
}
}
else {
pResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
// System.out.println(" << CachedEntity >> Not modified: " + toString());
if (isStale(pRequest)) {
// Add warning header
// Warning: 110 <server>:<port> Content is stale
pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
}
}
}
/* Utility method to get Host header */
private static String getHost(CacheRequest pRequest) {
return pRequest.getServerName() + ":" + pRequest.getServerPort();
}
public void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException {
// if (!(pResponse instanceof CacheResponseWrapper)) {
// throw new IllegalArgumentException("Response must be created by CachedEntity.createResponseWrapper()");
// }
//
// CacheResponseWrapper response = (CacheResponseWrapper) pResponse;
// if (response.isCachable()) {
mCache.registerContent(
mCacheURI,
pRequest,
pResponse instanceof WritableCachedResponse ? ((WritableCachedResponse) pResponse).getCachedResponse() : pResponse
);
// }
// else {
// Else store that the response for this request is not cachable
// pRequest.setAttribute(ATTRIB_NOT_CACHEABLE, Boolean.TRUE);
// TODO: Store this in HTTPCache, for subsequent requests to same resource?
// }
}
public boolean isStale(CacheRequest pRequest) {
return mCache.isContentStale(mCacheURI, pRequest);
}
public WritableCachedResponse createCachedResponse() {
return new WritableCachedResponseImpl();
}
public int hashCode() {
return (mCacheURI != null ? mCacheURI.hashCode() : 0) + 1397;
}
public boolean equals(Object pOther) {
return pOther instanceof CachedEntityImpl &&
((mCacheURI == null && ((CachedEntityImpl) pOther).mCacheURI == null) ||
mCacheURI != null && mCacheURI.equals(((CachedEntityImpl) pOther).mCacheURI));
}
public String toString() {
return "CachedEntity[URI=" + mCacheURI + "]";
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.OutputStream;
/**
* CachedResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java#3 $
*/
interface CachedResponse {
/**
* Writes the cached headers to the response
*
* @param pResponse the servlet response
*/
void writeHeadersTo(CacheResponse pResponse);
/**
* Writes the cahced content to the response
*
* @param pStream the response output stream
* @throws IOException if an I/O exception occurs during write
*/
void writeContentsTo(OutputStream pStream) throws IOException;
int getStatus();
// TODO: Map<String, List<String>> getHeaders()
/**
* Gets the header names of all headers set in this response.
*
* @return an array of {@code String}s
*/
String[] getHeaderNames();
/**
* Gets all header values set for the given header in this response. If the
* header is not set, {@code null} is returned.
*
* @param pHeaderName the header name
* @return an array of {@code String}s, or {@code null} if there is no
* such header in this response.
*/
String[] getHeaderValues(String pHeaderName);
/**
* Gets the first header value set for the given header in this response.
* If the header is not set, {@code null} is returned.
* Useful for headers that don't have multiple values, like
* {@code "Content-Type"} or {@code "Content-Length"}.
*
* @param pHeaderName the header name
* @return a {@code String}, or {@code null} if there is no
* such header in this response.
*/
String getHeaderValue(String pHeaderName);
/**
* Returns the size of this cached response in bytes.
*
* @return the size
*/
int size();
}

View File

@@ -0,0 +1,220 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.util.LinkedMap;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CachedResponseImpl
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java#4 $
*/
class CachedResponseImpl implements CachedResponse {
final protected Map<String, List<String>> mHeaders;
protected int mHeadersSize;
protected ByteArrayOutputStream mContent = null;
int mStatus;
protected CachedResponseImpl() {
mHeaders = new LinkedMap<String, List<String>>(); // Keep headers in insertion order
}
// For use by HTTPCache, when recreating CachedResponses from disk cache
CachedResponseImpl(final int pStatus, final LinkedMap<String, List<String>> pHeaders, final int pHeaderSize, final byte[] pContent) {
if (pHeaders == null) {
throw new IllegalArgumentException("headers == null");
}
mStatus = pStatus;
mHeaders = pHeaders;
mHeadersSize = pHeaderSize;
mContent = new FastByteArrayOutputStream(pContent);
}
public int getStatus() {
return mStatus;
}
/**
* Writes the cached headers to the response
*
* @param pResponse the response
*/
public void writeHeadersTo(final CacheResponse pResponse) {
String[] headers = getHeaderNames();
for (String header : headers) {
// HACK...
// Strip away internal headers
if (HTTPCache.HEADER_CACHED_TIME.equals(header)) {
continue;
}
// TODO: Replace Last-Modified with X-Cached-At? See CachedEntityImpl, line 50
String[] headerValues = getHeaderValues(header);
for (int i = 0; i < headerValues.length; i++) {
String headerValue = headerValues[i];
if (i == 0) {
pResponse.setHeader(header, headerValue);
}
else {
pResponse.addHeader(header, headerValue);
}
}
}
}
/**
* Writes the cahced content to the response
*
* @param pStream the response stream
* @throws java.io.IOException
*/
public void writeContentsTo(final OutputStream pStream) throws IOException {
if (mContent == null) {
throw new IOException("Cache is null, no content to write.");
}
mContent.writeTo(pStream);
}
/**
* Gets the header names of all headers set in this response.
*
* @return an array of {@code String}s
*/
public String[] getHeaderNames() {
Set<String> headers = mHeaders.keySet();
return headers.toArray(new String[headers.size()]);
}
/**
* Gets all header values set for the given header in this response. If the
* header is not set, {@code null} is returned.
*
* @param pHeaderName the header name
* @return an array of {@code String}s, or {@code null} if there is no
* such header in this response.
*/
public String[] getHeaderValues(final String pHeaderName) {
List<String> values = mHeaders.get(pHeaderName);
if (values == null) {
return null;
}
else {
return values.toArray(new String[values.size()]);
}
}
/**
* Gets the first header value set for the given header in this response.
* If the header is not set, {@code null} is returned.
* Useful for headers that don't have multiple values, like
* {@code "Content-Type"} or {@code "Content-Length"}.
*
* @param pHeaderName the header name
* @return a {@code String}, or {@code null} if there is no
* such header in this response.
*/
public String getHeaderValue(final String pHeaderName) {
List<String> values = mHeaders.get(pHeaderName);
return (values != null && values.size() > 0) ? values.get(0) : null;
}
public int size() {
// mContent.size() is exact size in bytes, mHeadersSize is an estimate
return (mContent != null ? mContent.size() : 0) + mHeadersSize;
}
public boolean equals(final Object pOther) {
if (this == pOther) {
return true;
}
if (pOther instanceof CachedResponseImpl) {
// "Fast"
return equalsImpl((CachedResponseImpl) pOther);
}
else if (pOther instanceof CachedResponse) {
// Slow
return equalsGeneric((CachedResponse) pOther);
}
return false;
}
private boolean equalsImpl(final CachedResponseImpl pOther) {
return mHeadersSize == pOther.mHeadersSize &&
(mContent == null ? pOther.mContent == null : mContent.equals(pOther.mContent)) &&
mHeaders.equals(pOther.mHeaders);
}
private boolean equalsGeneric(final CachedResponse pOther) {
if (size() != pOther.size()) {
return false;
}
String[] headers = getHeaderNames();
String[] otherHeaders = pOther.getHeaderNames();
if (!Arrays.equals(headers, otherHeaders)) {
return false;
}
if (headers != null) {
for (String header : headers) {
String[] values = getHeaderValues(header);
String[] otherValues = pOther.getHeaderValues(header);
if (!Arrays.equals(values, otherValues)) {
return false;
}
}
}
return true;
}
public int hashCode() {
int result;
result = mHeaders.hashCode();
result = 29 * result + mHeadersSize;
result = 37 * result + (mContent != null ? mContent.hashCode() : 0);
return result;
}
}

View File

@@ -0,0 +1,44 @@
package com.twelvemonkeys.servlet.cache;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* ClientCacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ClientCacheRequest.java#1 $
*/
public final class ClientCacheRequest extends AbstractCacheRequest {
private Map<String, List<String>> mParameters;
private Map<String, List<String>> mHeaders;
public ClientCacheRequest(final URI pRequestURI,final Map<String, List<String>> pParameters, final Map<String, List<String>> pHeaders) {
super(pRequestURI, "GET"); // TODO: Consider supporting more than get? At least HEAD and OPTIONS...
mParameters = normalizeMap(pParameters);
mHeaders = normalizeMap(pHeaders);
}
private <K, V> Map<K, V> normalizeMap(Map<K, V> pMap) {
return pMap == null ? Collections.<K, V>emptyMap() : Collections.unmodifiableMap(pMap);
}
public Map<String, List<String>> getParameters() {
return mParameters;
}
public Map<String, List<String>> getHeaders() {
return mHeaders;
}
public String getServerName() {
return getRequestURI().getAuthority();
}
public int getServerPort() {
return getRequestURI().getPort();
}
}

View File

@@ -0,0 +1,25 @@
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* ClientCacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ClientCacheResponse.java#2 $
*/
public final class ClientCacheResponse extends AbstractCacheResponse {
// It's quite useless to cahce the data either on disk or in memory, as it already is cached in the client's cache...
// It would be nice if we could bypass that...
public OutputStream getOutputStream() throws IOException {
throw new UnsupportedOperationException("Method getOutputStream not implemented"); // TODO: Implement
}
public InputStream getInputStream() {
throw new UnsupportedOperationException("Method getInputStream not implemented"); // TODO: Implement
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
package com.twelvemonkeys.servlet.cache;
import java.io.IOException;
/**
* ResponseResolver
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ResponseResolver.java#2 $
*/
public interface ResponseResolver {
void resolve(CacheRequest pRequest, CacheResponse pResponse) throws IOException, CacheException;
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.net.NetUtil;
import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
/**
* CacheResponseWrapper class description.
* <p/>
* Based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java#2 $
*/
class SerlvetCacheResponseWrapper extends HttpServletResponseWrapper {
private ServletResponseStreamDelegate mStreamDelegate;
private CacheResponse mCacheResponse;
private Boolean mCachable;
private int mStatus;
public SerlvetCacheResponseWrapper(final HttpServletResponse pServletResponse, final CacheResponse pResponse) {
super(pServletResponse);
mCacheResponse = pResponse;
init();
}
/*
NOTE: This class defers determining if a response is cachable until the
output stream is needed.
This it the reason for the somewhat complicated logic in the add/setHeader
methods below.
*/
private void init() {
mCachable = null;
mStatus = SC_OK;
mStreamDelegate = new ServletResponseStreamDelegate(this) {
protected OutputStream createOutputStream() throws IOException {
// Test if this request is really cachable, otherwise,
// just write through to underlying response, and don't cache
if (isCachable()) {
return mCacheResponse.getOutputStream();
}
else {
// TODO: We need to tell the cache about this, somehow...
writeHeaders(mCacheResponse, (HttpServletResponse) getResponse());
return super.getOutputStream();
}
}
};
}
private void writeHeaders(final CacheResponse pResponse, final HttpServletResponse pServletResponse) {
Map<String,List<String>> headers = pResponse.getHeaders();
for (Map.Entry<String, List<String>> header : headers.entrySet()) {
for (int i = 0; i < header.getValue().size(); i++) {
String value = header.getValue().get(i);
if (i == 0) {
pServletResponse.setHeader(header.getKey(), value);
}
else {
pServletResponse.addHeader(header.getKey(), value);
}
}
}
}
public boolean isCachable() {
// NOTE: Intentionally not synchronized
if (mCachable == null) {
mCachable = isCachableImpl();
}
return mCachable;
}
private boolean isCachableImpl() {
// TODO: This code is duped in the cache...
if (mStatus != SC_OK) {
return false;
}
// Vary: *
List<String> values = mCacheResponse.getHeaders().get(HTTPCache.HEADER_VARY);
if (values != null) {
for (String value : values) {
if ("*".equals(value)) {
return false;
}
}
}
// Cache-Control: no-cache, no-store, must-revalidate
values = mCacheResponse.getHeaders().get(HTTPCache.HEADER_CACHE_CONTROL);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")
|| StringUtil.contains(value, "no-store")
|| StringUtil.contains(value, "must-revalidate")) {
return false;
}
}
}
// Pragma: no-cache
values = mCacheResponse.getHeaders().get(HTTPCache.HEADER_PRAGMA);
if (values != null) {
for (String value : values) {
if (StringUtil.contains(value, "no-cache")) {
return false;
}
}
}
return true;
}
public void flushBuffer() throws IOException {
mStreamDelegate.flushBuffer();
}
public void resetBuffer() {
// Servlet 2.3
mStreamDelegate.resetBuffer();
}
public void reset() {
if (Boolean.FALSE.equals(mCachable)) {
super.reset();
}
// No else, might be cachable after all..
init();
}
public ServletOutputStream getOutputStream() throws IOException {
return mStreamDelegate.getOutputStream();
}
public PrintWriter getWriter() throws IOException {
return mStreamDelegate.getWriter();
}
public boolean containsHeader(String name) {
return mCacheResponse.getHeaders().get(name) != null;
}
public void sendError(int pStatusCode, String msg) throws IOException {
// NOT cachable
mStatus = pStatusCode;
super.sendError(pStatusCode, msg);
}
public void sendError(int pStatusCode) throws IOException {
// NOT cachable
mStatus = pStatusCode;
super.sendError(pStatusCode);
}
public void setStatus(int pStatusCode, String sm) {
// NOTE: This method is deprecated
setStatus(pStatusCode);
}
public void setStatus(int pStatusCode) {
// NOT cachable unless pStatusCode == 200 (or a FEW others?)
if (pStatusCode != SC_OK) {
mStatus = pStatusCode;
super.setStatus(pStatusCode);
}
}
public void sendRedirect(String pLocation) throws IOException {
// NOT cachable
mStatus = SC_MOVED_TEMPORARILY;
super.sendRedirect(pLocation);
}
public void setDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setDateHeader(pName, pValue);
}
mCacheResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue));
}
public void addDateHeader(String pName, long pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addDateHeader(pName, pValue);
}
mCacheResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue));
}
public void setHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setHeader(pName, pValue);
}
mCacheResponse.setHeader(pName, pValue);
}
public void addHeader(String pName, String pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addHeader(pName, pValue);
}
mCacheResponse.addHeader(pName, pValue);
}
public void setIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.setIntHeader(pName, pValue);
}
mCacheResponse.setHeader(pName, String.valueOf(pValue));
}
public void addIntHeader(String pName, int pValue) {
// If in write-trough-mode, set headers directly
if (Boolean.FALSE.equals(mCachable)) {
super.addIntHeader(pName, pValue);
}
mCacheResponse.addHeader(pName, String.valueOf(pValue));
}
public final void setContentType(String type) {
setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
}
}

View File

@@ -0,0 +1,56 @@
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.List;
import java.util.Map;
/**
* ServletCacheRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ServletCacheRequest.java#1 $
*/
public final class ServletCacheRequest extends AbstractCacheRequest {
private final HttpServletRequest mRequest;
private Map<String, List<String>> mHeaders;
private Map<String, List<String>> mParameters;
protected ServletCacheRequest(final HttpServletRequest pRequest) {
super(URI.create(pRequest.getRequestURI()), pRequest.getMethod());
mRequest = pRequest;
}
public Map<String, List<String>> getHeaders() {
if (mHeaders == null) {
mHeaders = ServletUtil.headersAsMap(mRequest);
}
return mHeaders;
}
public Map<String, List<String>> getParameters() {
if (mParameters == null) {
mParameters = ServletUtil.parametersAsMap(mRequest);
}
return mParameters;
}
public String getServerName() {
return mRequest.getServerName();
}
public int getServerPort() {
return mRequest.getServerPort();
}
HttpServletRequest getRequest() {
return mRequest;
}
}

View File

@@ -0,0 +1,46 @@
package com.twelvemonkeys.servlet.cache;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
/**
* ServletCacheResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ServletCacheResponse.java#2 $
*/
public final class ServletCacheResponse extends AbstractCacheResponse {
private HttpServletResponse mResponse;
public ServletCacheResponse(HttpServletResponse pResponse) {
mResponse = pResponse;
}
public OutputStream getOutputStream() throws IOException {
return mResponse.getOutputStream();
}
@Override
public void setStatus(int pStatusCode) {
mResponse.setStatus(pStatusCode);
super.setStatus(pStatusCode);
}
@Override
public void addHeader(String pHeaderName, String pHeaderValue) {
mResponse.addHeader(pHeaderName, pHeaderValue);
super.addHeader(pHeaderName, pHeaderValue);
}
@Override
public void setHeader(String pHeaderName, String pHeaderValue) {
mResponse.setHeader(pHeaderName, pHeaderValue);
super.setHeader(pHeaderName, pHeaderValue);
}
HttpServletResponse getResponse() {
return mResponse;
}
}

View File

@@ -0,0 +1,40 @@
package com.twelvemonkeys.servlet.cache;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* ServletResponseResolver
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/ServletResponseResolver.java#2 $
*/
final class ServletResponseResolver implements ResponseResolver {
final private ServletCacheRequest mRequest;
final private ServletCacheResponse mResponse;
final private FilterChain mChain;
ServletResponseResolver(final ServletCacheRequest pRequest, final ServletCacheResponse pResponse, final FilterChain pChain) {
mRequest = pRequest;
mResponse = pResponse;
mChain = pChain;
}
public void resolve(final CacheRequest pRequest, final CacheResponse pResponse) throws IOException, CacheException {
// Need only wrap if pResponse is not mResponse...
HttpServletResponse response = pResponse == mResponse ? mResponse.getResponse() : new SerlvetCacheResponseWrapper(mResponse.getResponse(), pResponse);
try {
mChain.doFilter(mRequest.getRequest(), response);
}
catch (ServletException e) {
throw new CacheException(e);
}
finally {
response.flushBuffer();
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import java.io.OutputStream;
/**
* WritableCachedResponse
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java#2 $
*/
public interface WritableCachedResponse extends CachedResponse, CacheResponse {
/**
* Gets the {@code OutputStream} for this cached response.
* This allows a client to write to the cached response.
*
* @return the {@code OutputStream} for this response.
*/
OutputStream getOutputStream();
/**
* Sets a header key/value pair for this response.
* Any prior header value for the given header key will be overwritten.
*
* @see #addHeader(String, String)
*
* @param pName the header name
* @param pValue the header value
*/
void setHeader(String pName, String pValue);
/**
* Adds a header key/value pair for this response.
* If a value allready exists for the given key, the value will be appended.
*
* @see #setHeader(String, String)
*
* @param pName the header name
* @param pValue the header value
*/
void addHeader(String pName, String pValue);
/**
* Returns the final (immutable) {@code CachedResponse} created by this
* {@code WritableCachedResponse}.
*
* @return the {@code CachedResponse}
*/
CachedResponse getCachedResponse();
}

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.cache;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.net.NetUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* WritableCachedResponseImpl
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java#3 $
*/
class WritableCachedResponseImpl implements WritableCachedResponse {
private final CachedResponseImpl mCachedResponse;
/**
* Creates a {@code WritableCachedResponseImpl}.
*/
protected WritableCachedResponseImpl() {
mCachedResponse = new CachedResponseImpl();
// Hmmm..
setHeader(HTTPCache.HEADER_CACHED_TIME, NetUtil.formatHTTPDate(System.currentTimeMillis()));
}
public CachedResponse getCachedResponse() {
return mCachedResponse;
}
public void setHeader(String pName, String pValue) {
setHeader(pName, pValue, false);
}
public void addHeader(String pName, String pValue) {
setHeader(pName, pValue, true);
}
public Map<String, List<String>> getHeaders() {
return mCachedResponse.mHeaders;
}
/**
*
* @param pName the header name
* @param pValue the new header value
* @param pAdd {@code true} if the value should add to the list of values, not replace existing value
*/
private void setHeader(String pName, String pValue, boolean pAdd) {
// System.out.println(" ++ CachedResponse ++ " + (pAdd ? "addHeader(" : "setHeader(") + pName + ", " + pValue + ")");
// If adding, get list and append, otherwise replace list
List<String> values = null;
if (pAdd) {
values = mCachedResponse.mHeaders.get(pName);
}
if (values == null) {
values = new ArrayList<String>();
if (pAdd) {
// Add length of pName
mCachedResponse.mHeadersSize += (pName != null ? pName.length() : 0);
}
else {
// Remove length of potential replaced old values + pName
String[] oldValues = getHeaderValues(pName);
if (oldValues != null) {
for (String oldValue : oldValues) {
mCachedResponse.mHeadersSize -= oldValue.length();
}
}
else {
mCachedResponse.mHeadersSize += (pName != null ? pName.length() : 0);
}
}
}
// Add value, if not null
if (pValue != null) {
values.add(pValue);
// Add length of pValue
mCachedResponse.mHeadersSize += pValue.length();
}
// Always add to headers
mCachedResponse.mHeaders.put(pName, values);
}
public OutputStream getOutputStream() {
// TODO: Hmm.. Smells like DCL..?
if (mCachedResponse.mContent == null) {
createOutputStream();
}
return mCachedResponse.mContent;
}
public void setStatus(int pStatusCode) {
mCachedResponse.mStatus = pStatusCode;
}
public int getStatus() {
return mCachedResponse.getStatus();
}
private synchronized void createOutputStream() {
ByteArrayOutputStream cache = mCachedResponse.mContent;
if (cache == null) {
String contentLengthStr = getHeaderValue("Content-Length");
if (contentLengthStr != null) {
int contentLength = Integer.parseInt(contentLengthStr);
cache = new FastByteArrayOutputStream(contentLength);
}
else {
cache = new FastByteArrayOutputStream(1024);
}
mCachedResponse.mContent = cache;
}
}
public void writeHeadersTo(CacheResponse pResponse) {
mCachedResponse.writeHeadersTo(pResponse);
}
public void writeContentsTo(OutputStream pStream) throws IOException {
mCachedResponse.writeContentsTo(pStream);
}
public String[] getHeaderNames() {
return mCachedResponse.getHeaderNames();
}
public String[] getHeaderValues(String pHeaderName) {
return mCachedResponse.getHeaderValues(pHeaderName);
}
public String getHeaderValue(String pHeaderName) {
return mCachedResponse.getHeaderValue(pHeaderName);
}
public int size() {
return mCachedResponse.size();
}
public boolean equals(Object pOther) {
if (pOther instanceof WritableCachedResponse) {
// Take advantage of faster implementation
return mCachedResponse.equals(((WritableCachedResponse) pOther).getCachedResponse());
}
return mCachedResponse.equals(pOther);
}
public int hashCode() {
return mCachedResponse.hashCode();
}
}

View File

@@ -0,0 +1,3 @@
- Keep filter and servlet specific implementations in servlet module
- Move most of the implementation out of servlet module (HTTPCache + interfaces + abstract impl)
- Move client cache implementation classes out of servlet module, and to separate package

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
/**
* FileSizeExceededException
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java#1 $
*/
public class FileSizeExceededException extends FileUploadException {
public FileSizeExceededException(Throwable pCause) {
super(pCause.getMessage(), pCause);
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import javax.servlet.ServletException;
/**
* FileUploadException
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java#1 $
*/
public class FileUploadException extends ServletException {
public FileUploadException(String pMessage) {
super(pMessage);
}
public FileUploadException(String pMessage, Throwable pCause) {
super(pMessage, pCause);
}
public FileUploadException(Throwable pCause) {
super(pCause.getMessage(), pCause);
}
}

View File

@@ -0,0 +1,137 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import com.twelvemonkeys.servlet.GenericFilter;
import com.twelvemonkeys.servlet.ServletUtil;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
/**
* A servlet {@code Filter} for processing HTTP file upload requests, as
* specified by
* <a href="http://www.ietf.org/rfc/rfc1867.txt">Form-based File Upload in HTML (RFC1867)</a>.
*
* @see HttpFileUploadRequest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java#1 $
*/
public class FileUploadFilter extends GenericFilter {
private File mUploadDir;
private long mMaxFileSize = 1024 * 1024; // 1 MByte
/**
* This method is called by the server before the filter goes into service,
* and here it determines the file upload directory.
*
* @throws ServletException
*/
public void init() throws ServletException {
// Get the name of the upload directory.
String uploadDirParam = getInitParameter("uploadDir");
if (!StringUtil.isEmpty(uploadDirParam)) {
try {
URL uploadDirURL = getServletContext().getResource(uploadDirParam);
mUploadDir = FileUtil.toFile(uploadDirURL);
}
catch (MalformedURLException e) {
throw new ServletException(e.getMessage(), e);
}
}
if (mUploadDir == null) {
mUploadDir = ServletUtil.getTempDir(getServletContext());
}
}
/**
* Sets max filesize allowed for upload.
* <!-- used by automagic init -->
*
* @param pMaxSize
*/
// public void setMaxFileSize(String pMaxSize) {
// try {
// setMaxFileSize(Long.parseLong(pMaxSize));
// }
// catch (NumberFormatException e) {
// log("Error setting maxFileSize, using default: " + mMaxFileSize, e);
// }
// }
/**
* Sets max filesize allowed for upload.
*
* @param pMaxSize
*/
public void setMaxFileSize(long pMaxSize) {
log("maxFileSize=" + pMaxSize);
mMaxFileSize = pMaxSize;
}
/**
* Examines the request content type, and if it is a
* {@code multipart/*} request, wraps the request with a
* {@code HttpFileUploadRequest}.
*
* @param pRequest The servlet request
* @param pResponse The servlet response
* @param pChain The filter chain
*
* @throws ServletException
* @throws IOException
*/
public void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) pRequest;
// Get the content type from the request
String contentType = request.getContentType();
// If the content type is multipart, wrap
if (isMultipartFileUpload(contentType)) {
pRequest = new HttpFileUploadRequestWrapper(request, mUploadDir, mMaxFileSize);
}
pChain.doFilter(pRequest, pResponse);
}
private boolean isMultipartFileUpload(String pContentType) {
return pContentType != null && pContentType.startsWith("multipart/");
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import javax.servlet.http.HttpServletRequest;
/**
* This interface represents an HTTP file upload request, as specified by
* <a href="http://www.ietf.org/rfc/rfc1867.txt">Form-based File Upload in HTML (RFC1867)</a>.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java#1 $
*/
public interface HttpFileUploadRequest extends HttpServletRequest {
/**
* Returns the value of a request parameter as an {@code UploadedFile},
* or {@code null} if the parameter does not exist.
* You should only use this method when you are sure the parameter has only
* one value.
*
* @param pName the name of the requested parameter
* @return a {@code UoploadedFile} or {@code null}
*
* @see #getUploadedFiles(String)
*/
UploadedFile getUploadedFile(String pName);
/**
* Returns an array of {@code UploadedFile} objects containing all the
* values for the given request parameter,
* or {@code null} if the parameter does not exist.
*
* @param pName the name of the requested parameter
* @return an array of {@code UoploadedFile}s or {@code null}
*/
UploadedFile[] getUploadedFiles(String pName);
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.ServletException;
import java.io.File;
import java.util.*;
/**
* An {@code HttpFileUploadRequest} implementation, based on
* <a href="http://jakarta.apache.org/commons/fileupload/">Jakarta Commons FileUpload</a>.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java#1 $
*/
class HttpFileUploadRequestWrapper extends HttpServletRequestWrapper implements HttpFileUploadRequest {
private final Map<String, String[]> mParameters = new HashMap<String, String[]>();
private final Map<String, UploadedFile[]> mFiles = new HashMap<String, UploadedFile[]>();
public HttpFileUploadRequestWrapper(HttpServletRequest pRequest, File pUploadDir, long pMaxSize) throws ServletException {
super(pRequest);
DiskFileItemFactory factory = new DiskFileItemFactory(
128 * 1024, // 128 KByte
new File(pUploadDir.getAbsolutePath())
);
FileUpload upload = new FileUpload(factory);
upload.setSizeMax(pMaxSize);
// TODO: Defer request parsing??
try {
//noinspection unchecked
List<FileItem> items = upload.parseRequest(new ServletRequestContext(pRequest));
for (FileItem item : items) {
if (item.isFormField()) {
processFormField(item.getFieldName(), item.getString());
}
else {
processeFile(item);
}
}
}
catch (FileUploadBase.SizeLimitExceededException e) {
throw new FileSizeExceededException(e);
}
catch (org.apache.commons.fileupload.FileUploadException e) {
throw new FileUploadException(e);
}
}
private void processeFile(final FileItem pItem) {
UploadedFile value = new UploadedFileImpl(pItem);
String name = pItem.getFieldName();
UploadedFile[] values;
UploadedFile[] oldValues = mFiles.get(name);
if (oldValues != null) {
values = new UploadedFile[oldValues.length + 1];
System.arraycopy(oldValues, 0, values, 0, oldValues.length);
values[oldValues.length] = value;
}
else {
values = new UploadedFile[] {value};
}
mFiles.put(name, values);
// Also add to normal fields
processFormField(name, value.getName());
}
private void processFormField(String pName, String pValue) {
// Multiple parameter values are not that common, so it's
// probably faster to just use arrays...
// TODO: Research and document...
String[] values;
String[] oldValues = mParameters.get(pName);
if (oldValues != null) {
values = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, values, 0, oldValues.length);
values[oldValues.length] = pValue;
}
else {
values = new String[] {pValue};
}
mParameters.put(pName, values);
}
public Map getParameterMap() {
// TODO: The spec dicates immutable map, but what about the value arrays?!
// Probably just leave as-is, for performance
return Collections.unmodifiableMap(mParameters);
}
public Enumeration getParameterNames() {
return Collections.enumeration(mParameters.keySet());
}
public String getParameter(String pString) {
String[] values = getParameterValues(pString);
return values != null ? values[0] : null;
}
public String[] getParameterValues(String pString) {
// TODO: Optimize?
return mParameters.get(pString).clone();
}
public UploadedFile getUploadedFile(String pName) {
UploadedFile[] files = getUploadedFiles(pName);
return files != null ? files[0] : null;
}
public UploadedFile[] getUploadedFiles(String pName) {
// TODO: Optimize?
return mFiles.get(pName).clone();
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
/**
* This class represents an uploaded file.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java#1 $
*/
public interface UploadedFile {
/**
* Returns the length of file, in bytes.
*
* @return length of file
*/
long length();
/**
* Returns the original file name (from client).
*
* @return original name
*/
String getName();
/**
* Returns the content type of the file.
*
* @return the content type
*/
String getContentType();
/**
* Returns the file data, as an {@code InputStream}.
* The file data may be read from disk, or from an in-memory source,
* depending on implementation.
*
* @return an {@code InputStream} containing the file data
* @throws IOException
* @throws RuntimeException
*/
InputStream getInputStream() throws IOException;
/**
* Writes the file data to the given {@code File}.
* Note that implementations are free to optimize this to a rename
* operation, if the file is allready cached to disk.
*
* @param pFile the {@code File} (file name) to write to.
* @throws IOException
* @throws RuntimeException
*/
void writeTo(File pFile) throws IOException;
// TODO: void delete()?
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.fileupload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
/**
* An {@code UploadedFile} implementation, based on
* <a href="http://jakarta.apache.org/commons/fileupload/">Jakarta Commons FileUpload</a>.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java#1 $
*/
class UploadedFileImpl implements UploadedFile {
private final FileItem mItem;
public UploadedFileImpl(FileItem pItem) {
if (pItem == null) {
throw new IllegalArgumentException("fileitem == null");
}
mItem = pItem;
}
public String getContentType() {
return mItem.getContentType();
}
public InputStream getInputStream() throws IOException {
return mItem.getInputStream();
}
public String getName() {
return mItem.getName();
}
public long length() {
return mItem.getSize();
}
public void writeTo(File pFile) throws IOException {
try {
mItem.write(pFile);
}
catch(RuntimeException e) {
throw e;
}
catch (IOException e) {
throw e;
}
catch (FileUploadException e) {
// We deliberately change this exception to an IOException, as it really is
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
catch (Exception e) {
// Should not really happen, ever
throw new RuntimeException(e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.gzip;
import com.twelvemonkeys.servlet.GenericFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* A filter to reduce the output size of web resources.
* <p/>
* The HTTP protocol supports compression of the content to reduce network
* bandwidth. The important headers involved, are the {@code Accept-Encoding}
* request header, and the {@code Content-Encoding} response header.
* This feature can be used to further reduce the number of bytes transferred
* over the network, at the cost of some extra processing time at both endpoints.
* Most modern browsers supports compression in GZIP format, which is fairly
* efficient in cost/compression ratio.
* <p/>
* The filter tests for the presence of an {@code Accept-Encoding} header with a
* value of {@code "gzip"} (several different encoding header values are
* possible in one header). If not present, the filter simply passes the
* request/response pair through, leaving it untouched. If present, the
* {@code Content-Encoding} header is set, with the value {@code "gzip"},
* and the response is wrapped.
* The response output stream is wrapped in a
* {@link java.util.zip.GZIPOutputStream} which performs the GZIP encoding.
* For efficiency, the filter does not buffer the response, but writes through
* the gzipped output stream.
* <p/>
* <b>Configuration</b><br/>
* To use {@code GZIPFilter} in your web-application, you simply need to add it
* to your web descriptor ({@code web.xml}). If using a servlet container that
* supports the Servlet 2.4 spec, the new {@code dispatcher} element should be
* used, and set to {@code REQUEST/FORWARD}, to make sure the filter is invoked
* only once for requests.
* If using an older web descriptor, set the {@code init-param}
* {@code "once-per-request"} to {@code "true"} (this will have the same effect,
* but might perform slightly worse than the 2.4 version).
* Please see the examples below.
* <b>Servlet 2.4 version, filter section:</b><br/>
* <pre>
* &lt;!-- GZIP Filter Configuration --&gt;
* &lt;filter&gt;
* &lt;filter-name&gt;gzip&lt;/filter-name&gt;
* &lt;filter-class&gt;com.twelvemonkeys.servlet.GZIPFilter&lt;/filter-class&gt;
* &lt;/filter&gt;
* </pre>
* <b>Filter-mapping section:</b><br/>
* <pre>
* &lt;!-- GZIP Filter Mapping --&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;gzip&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.html&lt;/url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* &lt;filter-mapping&gt;
* &lt;filter-name&gt;gzip&lt;/filter-name&gt;
* &lt;url-pattern&gt;*.jsp&lt; /url-pattern&gt;
* &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
* &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
* &lt;/filter-mapping&gt;
* </pre>
* <p/>
* Based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
* Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
* <p/>
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java#1 $
*/
public class GZIPFilter extends GenericFilter {
{
mOncePerRequest = true;
}
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
// Can only filter HTTP responses
if (pRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) pRequest;
HttpServletResponse response = (HttpServletResponse) pResponse;
// If GZIP is supported, use compression
String accept = request.getHeader("Accept-Encoding");
if (accept != null && accept.indexOf("gzip") != -1) {
//System.out.println("GZIP supported, compressing.");
// TODO: Set Vary: Accept-Encoding ?!
GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response);
try {
pChain.doFilter(pRequest, wrapped);
}
finally {
wrapped.flushResponse();
}
return;
}
}
// Else, contiue chain
pChain.doFilter(pRequest, pResponse);
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.gzip;
import com.twelvemonkeys.servlet.OutputStreamAdapter;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.zip.GZIPOutputStream;
/**
* GZIPResponseWrapper class description.
* <p/>
* Based on ideas and code found in the ONJava article
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two Servlet Filters Every Web Application Should Have</a>
* by Jayson Falkner.
*
* @author Jayson Falkner
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java#1 $
*/
public class GZIPResponseWrapper extends HttpServletResponseWrapper {
protected ServletOutputStream mOut = null;
protected PrintWriter mWriter = null;
protected GZIPOutputStream mGZIPOut = null;
protected int mContentLength = -1;
public GZIPResponseWrapper(HttpServletResponse response) {
super(response);
response.addHeader("Content-Encoding", "gzip");
}
public ServletOutputStream createOutputStream() throws IOException {
// FIX: Write directly to servlet output stream, for faster responses.
// Relies on chunked streams, or buffering in the servlet engine.
if (mContentLength >= 0) {
mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream(), mContentLength);
}
else {
mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream());
}
// Wrap in ServletOutputStream and return
return new OutputStreamAdapter(mGZIPOut);
}
// TODO: Move this to flushbuffer or something? Hmmm..
public void flushResponse() {
try {
try {
// Finish GZIP encodig
if (mGZIPOut != null) {
mGZIPOut.finish();
}
flushBuffer();
}
finally {
// Close stream
if (mWriter != null) {
mWriter.close();
}
else {
if (mOut != null) {
mOut.close();
}
}
}
}
catch (IOException e) {
// TODO: Fix this one...
e.printStackTrace();
}
}
public void flushBuffer() throws IOException {
if (mWriter != null) {
mWriter.flush();
}
else if (mOut != null) {
mOut.flush();
}
}
public ServletOutputStream getOutputStream() throws IOException {
if (mWriter != null) {
throw new IllegalStateException("getWriter() has already been called!");
}
if (mOut == null) {
mOut = createOutputStream();
}
return (mOut);
}
public PrintWriter getWriter() throws IOException {
if (mWriter != null) {
return (mWriter);
}
if (mOut != null) {
throw new IllegalStateException("getOutputStream() has already been called!");
}
mOut = createOutputStream();
// TODO: This is wrong. Should use getCharacterEncoding() or "ISO-8859-1" if gCE returns null.
mWriter = new PrintWriter(new OutputStreamWriter(mOut, "UTF-8"));
return (mWriter);
}
public void setContentLength(int pLength) {
// NOTE: Do not call super, as we will shrink the size.
mContentLength = pLength;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import javax.servlet.ServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
/**
* AWTImageFilterAdapter
*
* @author $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java#1 $
*
*/
public class AWTImageFilterAdapter extends ImageFilter {
private java.awt.image.ImageFilter mFilter = null;
public void setImageFilter(String pFilterClass) {
try {
Class filterClass = Class.forName(pFilterClass);
mFilter = (java.awt.image.ImageFilter) filterClass.newInstance();
}
catch (ClassNotFoundException e) {
log("Could not load filter class.", e);
}
catch (InstantiationException e) {
log("Could not instantiate filter.", e);
}
catch (IllegalAccessException e) {
log("Could not access filter class.", e);
}
}
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
// Filter
Image img = ImageUtil.filter(pImage, mFilter);
// Create BufferedImage & return
return ImageUtil.toBuffered(img, BufferedImage.TYPE_INT_RGB); // TODO: This is for JPEG only...
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import javax.servlet.ServletRequest;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.RenderedImage;
/**
* BufferedImageOpAdapter
*
* @author $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java#1 $
*
*/
public class BufferedImageOpAdapter extends ImageFilter {
private BufferedImageOp mFilter = null;
public void setImageFilter(String pFilterClass) {
try {
Class filterClass = Class.forName(pFilterClass);
mFilter = (BufferedImageOp) filterClass.newInstance();
}
catch (ClassNotFoundException e) {
log("Could not instantiate filter class.", e);
}
catch (InstantiationException e) {
log("Could not instantiate filter.", e);
}
catch (IllegalAccessException e) {
log("Could not access filter class.", e);
}
}
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
// Filter & return
return mFilter.filter(pImage, null);
}
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.zip.CRC32;
/**
* Creates a minimal 1 x 1 pixel PNG image, in a color specified by the
* {@code "color"} parameter. The color is HTML-style #RRGGBB, with two
* digits hex number for red, green and blue (the hash, '#', is optional).
* <p/>
* The class does only byte manipulation, there is no server-side image
* processing involving AWT ({@code Toolkit} class) of any kind.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java#2 $
*/
public class ColorServlet extends GenericServlet {
private final static String RGB_PARAME = "color";
// A minimal, one color indexed PNG
private final static byte[] PNG_IMG = new byte[]{
(byte) 0x89, (byte) 'P', (byte) 'N', (byte) 'G', // PNG signature (8 bytes)
0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, // IHDR length (13)
(byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R', // Image header
0x00, 0x00, 0x00, 0x01, // width
0x00, 0x00, 0x00, 0x01, // height
0x01, 0x03, 0x00, 0x00, 0x00, // bits, color type, compression, filter, interlace
0x25, (byte) 0xdb, 0x56, (byte) 0xca, // IHDR CRC
0x00, 0x00, 0x00, 0x03, // PLTE length (3)
(byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E', // Palette
0x00, 0x00, (byte) 0xff, // red, green, blue (updated by this servlet)
(byte) 0x8a, (byte) 0x78, (byte) 0xd2, 0x57, // PLTE CRC
0x00, 0x00, 0x00, 0x0a, // IDAT length (10)
(byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T', // Image data
0x78, (byte) 0xda, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
(byte) 0xe5, 0x27, (byte) 0xde, (byte) 0xfc, // IDAT CRC
0x00, 0x00, 0x00, 0x00, // IEND length (0)
(byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D', // Image end
(byte) 0xae, (byte) 0x42, (byte) 0x60, (byte) 0x82 // IEND CRC
};
private final static int PLTE_CHUNK_START = 37; // after chunk length
private final static int PLTE_CHUNK_LENGTH = 7; // chunk name & data
private final static int RED_IDX = 4;
private final static int GREEN_IDX = RED_IDX + 1;
private final static int BLUE_IDX = GREEN_IDX + 1;
private final CRC32 mCRC = new CRC32();
/**
* Creates a ColorDroplet.
*/
public ColorServlet() {
super();
}
/**
* Renders the 1 x 1 single color PNG to the response.
*
* @see ColorServlet class description
*
* @param pRequest the request
* @param pResponse the response
*
* @throws IOException
* @throws ServletException
*/
public void service(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
int red = 0;
int green = 0;
int blue = 0;
// Get color parameter and parse color
String rgb = pRequest.getParameter(RGB_PARAME);
if (rgb != null && rgb.length() >= 6 && rgb.length() <= 7) {
int index = 0;
// If the hash ('#') character is included, skip it.
if (rgb.length() == 7) {
index++;
}
try {
// Two digit hex for each color
String r = rgb.substring(index, index += 2);
red = Integer.parseInt(r, 0x10);
String g = rgb.substring(index, index += 2);
green = Integer.parseInt(g, 0x10);
String b = rgb.substring(index, index += 2);
blue = Integer.parseInt(b, 0x10);
}
catch (NumberFormatException nfe) {
log("Wrong color format for ColorDroplet: " + rgb + ". Must be RRGGBB.");
}
}
// Set MIME type for PNG
pResponse.setContentType("image/png");
ServletOutputStream out = pResponse.getOutputStream();
try {
// Write header (and palette chunk length)
out.write(PNG_IMG, 0, PLTE_CHUNK_START);
// Create palette chunk, excl lenght, and write
byte[] palette = makePalette(red, green, blue);
out.write(palette);
// Write image data until end
int pos = PLTE_CHUNK_START + PLTE_CHUNK_LENGTH + 4;
out.write(PNG_IMG, pos, PNG_IMG.length - pos);
}
finally {
out.flush();
}
}
/**
* Updates the CRC for a byte array. Note that the byte array must be at
* least {@code pOff + pLen + 4} bytes long, as the CRC is stored in the
* 4 last bytes.
*
* @param pBytes the bytes to create CRC for
* @param pOff the offset into the byte array to create CRC for
* @param pLen the length of the byte array to create CRC for
*/
private void updateCRC(byte[] pBytes, int pOff, int pLen) {
int value;
synchronized (mCRC) {
mCRC.reset();
mCRC.update(pBytes, pOff, pLen);
value = (int) mCRC.getValue();
}
pBytes[pOff + pLen ] = (byte) ((value >> 24) & 0xff);
pBytes[pOff + pLen + 1] = (byte) ((value >> 16) & 0xff);
pBytes[pOff + pLen + 2] = (byte) ((value >> 8) & 0xff);
pBytes[pOff + pLen + 3] = (byte) ( value & 0xff);
}
/**
* Creates a PNG palette (PLTE) chunk with one color.
* The palette chunk data is always 3 bytes in length (one byte per color
* component).
* The returned byte array is then {@code 4 + 3 + 4 = 11} bytes,
* including chunk header, data and CRC.
*
* @param pRed the red component
* @param pGreen the reen component
* @param pBlue the blue component
*
* @return the bytes for the PLTE chunk, including CRC (but not length)
*/
private byte[] makePalette(int pRed, int pGreen, int pBlue) {
byte[] palette = new byte[PLTE_CHUNK_LENGTH + 4];
System.arraycopy(PNG_IMG, PLTE_CHUNK_START, palette, 0, PLTE_CHUNK_LENGTH);
palette[RED_IDX] = (byte) pRed;
palette[GREEN_IDX] = (byte) pGreen;
palette[BLUE_IDX] = (byte) pBlue;
updateCRC(palette, 0, PLTE_CHUNK_LENGTH);
return palette;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import javax.servlet.ServletRequest;
import java.awt.image.RenderedImage;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* ComposeFilter
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java#1 $
*/
public class ComposeFilter extends ImageFilter {
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
// 1. Load different image, locally (using ServletContext.getResource)
// - Allow loading other filtered sources, or servlets? For example to
// apply filename or timestamps?
// - Allow applying text directly? Variables?
// 2. Apply transformations from config
// - Relative positioning
// - Relative scaling
// - Repeat (fill-pattern)?
// - Rotation?
// - Transparency?
// - Background or foreground (layers)?
// 3. Apply loaded image to original image (or vice versa?).
return pImage;
}
}

View File

@@ -0,0 +1,436 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.*;
/**
* This filter implements server side content negotiation and transcoding for
* images.
*
* @todo Add support for automatic recognition of known browsers, to avoid
* unneccessary conversion (as IE supports PNG, the latests FireFox supports
* JPEG and GIF, etc. even though they both don't explicitly list these formats
* in their Accept headers).
*/
public class ContentNegotiationFilter extends ImageFilter {
private final static String MIME_TYPE_IMAGE_PREFIX = "image/";
private static final String MIME_TYPE_IMAGE_ANY = MIME_TYPE_IMAGE_PREFIX + "*";
private static final String MIME_TYPE_ANY = "*/*";
private static final String HTTP_HEADER_ACCEPT = "Accept";
private static final String HTTP_HEADER_VARY = "Vary";
protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
private static final String FORMAT_JPEG = "image/jpeg";
private static final String FORMAT_WBMP = "image/wbmp";
private static final String FORMAT_GIF = "image/gif";
private static final String FORMAT_PNG = "image/png";
private final static String[] sKnownFormats = new String[] {
FORMAT_JPEG, FORMAT_PNG, FORMAT_GIF, FORMAT_WBMP
};
private float[] mKnownFormatQuality = new float[] {
1f, 1f, 0.99f, 0.5f
};
private HashMap<String, Float> mFormatQuality; // HashMap, as I need to clone this for each request
private final Object mLock = new Object();
/*
private Pattern[] mKnownAgentPatterns;
private String[] mKnownAgentAccpets;
*/
{
// Hack: Make sure the filter don't trigger all the time
// See: super.trigger(ServletRequest)
mTriggerParams = new String[] {};
}
/*
public void setAcceptMappings(String pPropertiesFile) {
// NOTE: Supposed to be:
// <agent-name>=<reg-exp>
// <agent-name>.accept=<http-accept-header>
Properties mappings = new Properties();
try {
mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
List patterns = new ArrayList();
List accepts = new ArrayList();
for (Iterator iterator = mappings.keySet().iterator(); iterator.hasNext();) {
String agent = (String) iterator.next();
if (agent.endsWith(".accept")) {
continue;
}
try {
patterns.add(Pattern.compile((String) mappings.get(agent)));
// TODO: Consider preparsing ACCEPT header??
accepts.add(mappings.get(agent + ".accept"));
}
catch (PatternSyntaxException e) {
log("Could not parse User-Agent identification for " + agent, e);
}
mKnownAgentPatterns = (Pattern[]) patterns.toArray(new Pattern[patterns.size()]);
mKnownAgentAccpets = (String[]) accepts.toArray(new String[accepts.size()]);
}
}
catch (IOException e) {
log("Could not read accetp-mappings properties file: " + pPropertiesFile, e);
}
}
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
// NOTE: super invokes trigger() and image specific doFilter() if needed
super.doFilterImpl(pRequest, pResponse, pChain);
if (pResponse instanceof HttpServletResponse) {
// Update the Vary HTTP header field
((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT);
//((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_USER_AGENT);
}
}
/**
* Makes sure the filter triggers for unknown file formats.
*
* @param pRequest the request
* @return {@code true} if the filter should execute, {@code false}
* otherwise
*/
protected boolean trigger(ServletRequest pRequest) {
boolean trigger = false;
if (pRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) pRequest;
String accept = getAcceptedFormats(request);
String originalFormat = getServletContext().getMimeType(request.getRequestURI());
//System.out.println("Accept: " + accept);
//System.out.println("Original format: " + originalFormat);
// Only override original format if it is not accpeted by the client
// Note: Only explicit matches are okay, */* or image/* is not.
if (!StringUtil.contains(accept, originalFormat)) {
trigger = true;
}
}
// Call super, to allow content negotiation even though format is supported
return trigger || super.trigger(pRequest);
}
private String getAcceptedFormats(HttpServletRequest pRequest) {
return pRequest.getHeader(HTTP_HEADER_ACCEPT);
}
/*
private String getAcceptedFormats(HttpServletRequest pRequest) {
String accept = pRequest.getHeader(HTTP_HEADER_ACCEPT);
// Check if User-Agent is in list of known agents
if (mKnownAgentPatterns != null) {
String agent = pRequest.getHeader(HTTP_HEADER_USER_AGENT);
for (int i = 0; i < mKnownAgentPatterns.length; i++) {
Pattern pattern = mKnownAgentPatterns[i];
if (pattern.matcher(agent).matches()) {
// Merge known with real accpet, in case plugins add extra capabilities
accept = mergeAccept(mKnownAgentAccpets[i], accept);
System.out.println("--> User-Agent: " + agent + " accepts: " + accept);
return accept;
}
}
}
System.out.println("No agent match, defaulting to Accept header: " + accept);
return accept;
}
private String mergeAccept(String pKnown, String pAccept) {
// TODO: Make sure there are no duplicates...
return pKnown + ", " + pAccept;
}
*/
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
if (pRequest instanceof HttpServletRequest) {
HttpServletRequest request = (HttpServletRequest) pRequest;
Map<String, Float> formatQuality = getFormatQualityMapping();
// TODO: Consider adding original format, and use as fallback in worst case?
// TODO: Original format should have some boost, to avoid unneccesary convertsion?
// Update source quality settings from image properties
adjustQualityFromImage(formatQuality, pImage);
//System.out.println("Source quality mapping: " + formatQuality);
adjustQualityFromAccept(formatQuality, request);
//System.out.println("Final media scores: " + formatQuality);
// Find the formats with the highest quality factor, and use the first (predictable)
String acceptable = findBestFormat(formatQuality);
//System.out.println("Acceptable: " + acceptable);
// Send HTTP 406 Not Acceptable
if (acceptable == null) {
if (pResponse instanceof HttpServletResponse) {
((HttpServletResponse) pResponse).sendError(HttpURLConnection.HTTP_NOT_ACCEPTABLE);
}
return null;
}
else {
// TODO: Only if the format was changed!
// Let other filters/caches/proxies know we changed the image
}
// Set format
pResponse.setOutputContentType(acceptable);
//System.out.println("Set format: " + acceptable);
}
return pImage;
}
private Map<String, Float> getFormatQualityMapping() {
synchronized(mLock) {
if (mFormatQuality == null) {
mFormatQuality = new HashMap<String, Float>();
// Use ImageIO to find formats we can actually write
String[] formats = ImageIO.getWriterMIMETypes();
// All known formats qs are initially 1.0
// Others should be 0.1 or something like that...
for (String format : formats) {
mFormatQuality.put(format, getKnownFormatQuality(format));
}
}
}
//noinspection unchecked
return (Map<String, Float>) mFormatQuality.clone();
}
/**
* Finds the best available format.
*
* @param pFormatQuality the format to quality mapping
* @return the mime type of the best available format
*/
private static String findBestFormat(Map<String, Float> pFormatQuality) {
String acceptable = null;
float acceptQuality = 0.0f;
for (Map.Entry<String, Float> entry : pFormatQuality.entrySet()) {
float qValue = entry.getValue();
if (qValue > acceptQuality) {
acceptQuality = qValue;
acceptable = entry.getKey();
}
}
//System.out.println("Accepted format: " + acceptable);
//System.out.println("Accepted quality: " + acceptQuality);
return acceptable;
}
/**
* Adjust quality from HTTP Accept header
*
* @param pFormatQuality the format to quality mapping
* @param pRequest the request
*/
private void adjustQualityFromAccept(Map<String, Float> pFormatQuality, HttpServletRequest pRequest) {
// Multiply all q factors with qs factors
// No q=.. should be interpreted as q=1.0
// Apache does some extras; if both explicit types and wildcards
// (without qaulity factor) are present, */* is interpreted as
// */*;q=0.01 and image/* is interpreted as image/*;q=0.02
// See: http://httpd.apache.org/docs-2.0/content-negotiation.html
String accept = getAcceptedFormats(pRequest);
//System.out.println("Accept: " + accept);
float anyImageFactor = getQualityFactor(accept, MIME_TYPE_IMAGE_ANY);
anyImageFactor = (anyImageFactor == 1) ? 0.02f : anyImageFactor;
float anyFactor = getQualityFactor(accept, MIME_TYPE_ANY);
anyFactor = (anyFactor == 1) ? 0.01f : anyFactor;
for (String format : pFormatQuality.keySet()) {
//System.out.println("Trying format: " + format);
String formatMIME = MIME_TYPE_IMAGE_PREFIX + format;
float qFactor = getQualityFactor(accept, formatMIME);
qFactor = (qFactor == 0f) ? Math.max(anyFactor, anyImageFactor) : qFactor;
adjustQuality(pFormatQuality, format, qFactor);
}
}
/**
*
* @param pAccept the accpet header value
* @param pContentType the content type to get the quality factor for
* @return the q factor of the given format, according to the accept header
*/
private static float getQualityFactor(String pAccept, String pContentType) {
float qFactor = 0;
int foundIndex = pAccept.indexOf(pContentType);
if (foundIndex >= 0) {
int startQIndex = foundIndex + pContentType.length();
if (startQIndex < pAccept.length() && pAccept.charAt(startQIndex) == ';') {
while (startQIndex < pAccept.length() && pAccept.charAt(startQIndex++) == ' ') {
// Skip over whitespace
}
if (pAccept.charAt(startQIndex++) == 'q' && pAccept.charAt(startQIndex++) == '=') {
int endQIndex = pAccept.indexOf(',', startQIndex);
if (endQIndex < 0) {
endQIndex = pAccept.length();
}
try {
qFactor = Float.parseFloat(pAccept.substring(startQIndex, endQIndex));
//System.out.println("Found qFactor " + qFactor);
}
catch (NumberFormatException e) {
// TODO: Determine what to do here.. Maybe use a very low value?
// Ahem.. The specs don't say anything about how to interpret a wrong q factor..
//System.out.println("Unparseable q setting; " + e.getMessage());
}
}
// TODO: Determine what to do here.. Maybe use a very low value?
// Unparseable q value, use 0
}
else {
// Else, assume quality is 1.0
qFactor = 1;
}
}
return qFactor;
}
/**
* Adjusts source quality settings from image properties.
*
* @param pFormatQuality the format to quality mapping
* @param pImage the image
*/
private static void adjustQualityFromImage(Map<String, Float> pFormatQuality, BufferedImage pImage) {
// NOTE: The values are all made-up. May need tuning.
// If pImage.getColorModel() instanceof IndexColorModel
// JPEG qs*=0.6
// If NOT binary or 2 color index
// WBMP qs*=0.5
// Else
// GIF qs*=0.02
// PNG qs*=0.9 // JPEG is smaller/faster
if (pImage.getColorModel() instanceof IndexColorModel) {
adjustQuality(pFormatQuality, FORMAT_JPEG, 0.6f);
if (pImage.getType() != BufferedImage.TYPE_BYTE_BINARY || ((IndexColorModel) pImage.getColorModel()).getMapSize() != 2) {
adjustQuality(pFormatQuality, FORMAT_WBMP, 0.5f);
}
}
else {
adjustQuality(pFormatQuality, FORMAT_GIF, 0.01f);
adjustQuality(pFormatQuality, FORMAT_PNG, 0.99f); // JPEG is smaller/faster
}
// If pImage.getColorModel().hasTransparentPixels()
// JPEG qs*=0.05
// WBMP qs*=0.05
// If NOT transparency == BITMASK
// GIF qs*=0.8
if (ImageUtil.hasTransparentPixels(pImage, true)) {
adjustQuality(pFormatQuality, FORMAT_JPEG, 0.009f);
adjustQuality(pFormatQuality, FORMAT_WBMP, 0.009f);
if (pImage.getColorModel().getTransparency() != Transparency.BITMASK) {
adjustQuality(pFormatQuality, FORMAT_GIF, 0.8f);
}
}
}
/**
* Updates the quality in the map.
*
* @param pFormatQuality Map<String,Float>
* @param pFormat the format
* @param pFactor the quality factor
*/
private static void adjustQuality(Map<String, Float> pFormatQuality, String pFormat, float pFactor) {
Float oldValue = pFormatQuality.get(pFormat);
if (oldValue != null) {
pFormatQuality.put(pFormat, oldValue * pFactor);
//System.out.println("New vallue after multiplying with " + pFactor + " is " + pFormatQuality.get(pFormat));
}
}
/**
* Gets the initial quality if this is a known format, otherwise 0.1
*
* @param pFormat the format name
* @return the q factor of the given format
*/
private float getKnownFormatQuality(String pFormat) {
for (int i = 0; i < sKnownFormats.length; i++) {
if (pFormat.equals(sKnownFormats[i])) {
return mKnownFormatQuality[i];
}
}
return 0.1f;
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.ServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
/**
* This Servlet is able to render a cropped part of an image.
*
* <P><HR><P>
*
* <A name="parameters"></A><STRONG>Parameters:</STRONG><BR>
* <DL>
* <DT>{@code cropX}</DT>
* <DD>integer, the new left edge of the image.
* <DT>{@code cropY}</DT>
* <DD>integer, the new top of the image.
* <DT>{@code cropWidth}</DT>
* <DD>integer, the new width of the image.
* <DT>{@code cropHeight}</DT>
* <DD>integer, the new height of the image.
* <DT>{@code cropUniform}</DT>
* <DD>boolean, wether or not uniform scalnig should be used. Default is
* {@code true}.
* <DT>{@code cropUnits}</DT>
* <DD>string, one of {@code PIXELS}, {@code PERCENT}.
* {@code PIXELS} is default.
*
* <!-- inherited from ScaleImage below: -->
*
* <DT>{@code image}</DT>
* <DD>string, the URL of the image to scale.
*
* <DT>{@code scaleX}</DT>
* <DD>integer, the new width of the image.
*
* <DT>{@code scaleY}</DT>
* <DD>integer, the new height of the image.
*
* <DT>{@code scaleUniform}</DT>
* <DD>boolean, wether or not uniform scalnig should be used. Default is
* {@code true}.
*
* <DT>{@code scaleUnits}</DT>
* <DD>string, one of {@code PIXELS}, {@code PERCENT}.
* {@code PIXELS} is default.
*
* <DT>{@code scaleQuality}</DT>
* <DD>string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
* {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
* {@code SCALE_DEFAULT} is default.
*
* </DL>
*
* @example
* &lt;IMG src="/crop/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=500&cropUniform=true"&gt;
*
* @example
* &lt;IMG src="/crop/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=50&cropUnits=PERCENT"&gt;
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java#1 $
*/
public class CropFilter extends ScaleFilter {
/** {@code cropX}*/
protected final static String PARAM_CROP_X = "cropX";
/** {@code cropY}*/
protected final static String PARAM_CROP_Y = "cropY";
/** {@code cropWidth}*/
protected final static String PARAM_CROP_WIDTH = "cropWidth";
/** {@code cropHeight}*/
protected final static String PARAM_CROP_HEIGHT = "cropHeight";
/** {@code cropUniform}*/
protected final static String PARAM_CROP_UNIFORM = "cropUniform";
/** {@code cropUnits}*/
protected final static String PARAM_CROP_UNITS = "cropUnits";
/**
* Reads the image from the requested URL, scales it, crops it, and returns
* it in the
* Servlet stream. See above for details on parameters.
*/
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
// Get crop coordinates
int x = ServletUtil.getIntParameter(pRequest, PARAM_CROP_X, -1);
int y = ServletUtil.getIntParameter(pRequest, PARAM_CROP_Y, -1);
int width = ServletUtil.getIntParameter(pRequest, PARAM_CROP_WIDTH, -1);
int height = ServletUtil.getIntParameter(pRequest, PARAM_CROP_HEIGHT, -1);
boolean uniform =
ServletUtil.getBooleanParameter(pRequest, PARAM_CROP_UNIFORM, false);
int units = getUnits(ServletUtil.getParameter(pRequest, PARAM_CROP_UNITS, null));
// Get crop bounds
Rectangle bounds =
getBounds(x, y, width, height, units, uniform, pImage);
// Return cropped version
return pImage.getSubimage((int) bounds.getX(), (int) bounds.getY(),
(int) bounds.getWidth(),
(int) bounds.getHeight());
//return scaled.getSubimage(x, y, width, height);
}
protected Rectangle getBounds(int pX, int pY, int pWidth, int pHeight,
int pUnits, boolean pUniform,
BufferedImage pImg) {
// Algoritm:
// Try to get x and y (default 0,0).
// Try to get width and height (default width-x, height-y)
//
// If percent, get ratio
//
// If uniform
//
int oldWidth = pImg.getWidth();
int oldHeight = pImg.getHeight();
float ratio;
if (pUnits == UNITS_PERCENT) {
if (pWidth >= 0 && pHeight >= 0) {
// Non-uniform
pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / 100f;
pWidth = (int) ((float) oldWidth * ratio);
pHeight = (int) ((float) oldHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / 100f;
pWidth = (int) ((float) oldWidth * ratio);
pHeight = (int) ((float) oldHeight * ratio);
}
// Else: No crop
}
//else if (UNITS_PIXELS.equalsIgnoreCase(pUnits)) {
else if (pUnits == UNITS_PIXELS) {
// Uniform
if (pUniform) {
if (pWidth >= 0 && pHeight >= 0) {
// Compute both ratios
ratio = (float) pWidth / (float) oldWidth;
float heightRatio = (float) pHeight / (float) oldHeight;
// Find the largest ratio, and use that for both
if (heightRatio < ratio) {
ratio = heightRatio;
pWidth = (int) ((float) oldWidth * ratio);
}
else {
pHeight = (int) ((float) oldHeight * ratio);
}
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / (float) oldWidth;
pHeight = (int) ((float) oldHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / (float) oldHeight;
pWidth = (int) ((float) oldWidth * ratio);
}
// Else: No crop
}
}
// Else: No crop
// Not specified, or outside bounds: Use original dimensions
if (pWidth < 0 || (pX < 0 && pWidth > oldWidth)
|| (pX >= 0 && (pX + pWidth) > oldWidth)) {
pWidth = (pX >= 0 ? oldWidth - pX : oldWidth);
}
if (pHeight < 0 || (pY < 0 && pHeight > oldHeight)
|| (pY >= 0 && (pY + pHeight) > oldHeight)) {
pHeight = (pY >= 0 ? oldHeight - pY : oldHeight);
}
// Center
if (pX < 0) {
pX = (pImg.getWidth() - pWidth) / 2;
}
if (pY < 0) {
pY = (pImg.getHeight() - pHeight) / 2;
}
//System.out.println("x: " + pX + " y: " + pY
// + " w: " + pWidth + " h " + pHeight);
return new Rectangle(pX, pY, pWidth, pHeight);
}
}

View File

@@ -0,0 +1,199 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.GenericFilter;
import javax.servlet.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.IOException;
/**
* Abstract base class for image filters. Automatically decoding and encoding of
* the image is handled in the {@code doFilterImpl} method.
*
* @see #doFilter(java.awt.image.BufferedImage,javax.servlet.ServletRequest,ImageServletResponse)
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java#2 $
*
*/
public abstract class ImageFilter extends GenericFilter {
protected String[] mTriggerParams = null;
/**
* The {@code doFilterImpl} method is called once, or each time a
* request/response pair is passed through the chain, depending on the
* {@link #mOncePerRequest} member variable.
*
* @see #mOncePerRequest
* @see com.twelvemonkeys.servlet.GenericFilter#doFilterImpl doFilter
* @see Filter#doFilter Filter.doFilter
*
* @param pRequest the servlet request
* @param pResponse the servlet response
* @param pChain the filter chain
*
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
throws IOException, ServletException {
//System.out.println("Starting filtering...");
// Test for trigger params
if (!trigger(pRequest)) {
//System.out.println("Passing request on to next in chain (skipping " + getFilterName() + ")...");
// Pass the request on
pChain.doFilter(pRequest, pResponse);
}
else {
// For images, we do post filtering only and need to wrap the response
ImageServletResponse imageResponse;
boolean encode;
if (pResponse instanceof ImageServletResponse) {
//System.out.println("Allready ImageServletResponse");
imageResponse = (ImageServletResponse) pResponse;
encode = false; // Allready wrapped, will be encoded later in the chain
}
else {
//System.out.println("Wrapping in ImageServletResponse");
imageResponse = new ImageServletResponseImpl(pRequest, pResponse, getServletContext());
encode = true; // This is first filter in chain, must encode when done
}
//System.out.println("Passing request on to next in chain...");
// Pass the request on
pChain.doFilter(pRequest, imageResponse);
//System.out.println("Post filtering...");
// Get image
//System.out.println("Getting image from ImageServletResponse...");
// Get the image from the wrapped response
RenderedImage image = imageResponse.getImage();
//System.out.println("Got image: " + image);
// Note: Image will be null if this is a HEAD request, the
// If-Modified-Since header is present, or similar.
if (image != null) {
// Do the image filtering
//System.out.println("Filtering image (" + getFilterName() + ")...");
image = doFilter(ImageUtil.toBuffered(image), pRequest, imageResponse);
//System.out.println("Done filtering.");
//System.out.println("Making image available...");
// Make image available to other filters (avoid unnecessary
// serializing/deserializing)
imageResponse.setImage(image);
//System.out.println("Done.");
if (encode) {
//System.out.println("Encoding image...");
// Encode image to original repsonse
if (image != null) {
// TODO: Be smarter than this...
// TODO: Make sure ETag is same, if image content is the same...
// Use ETag of original response (or derived from)
// Use last modified of original response? Or keep original resource's, don't set at all?
// TODO: Why weak ETag?
String etag = "W/\"" + Integer.toHexString(hashCode()) + "-" + Integer.toHexString(image.hashCode()) + "\"";
((ImageServletResponseImpl) imageResponse).setHeader("ETag", etag);
((ImageServletResponseImpl) imageResponse).setDateHeader("Last-Modified", (System.currentTimeMillis() / 1000) * 1000);
imageResponse.flush();
}
//System.out.println("Done encoding.");
}
}
}
//System.out.println("Filtering done.");
}
/**
* Tests if the filter should do image filtering/processing.
* <P/>
* This default implementation uses {@link #mTriggerParams} to test if:
* <dl>
* <dt>{@code mTriggerParams == null}</dt>
* <dd>{@code return true}</dd>
* <dt>{@code mTriggerParams != null}, loop through parameters, and test
* if {@code pRequest} contains the parameter. If match</dt>
* <dd>{@code return true}</dd>
* <dt>Otherwise</dt>
* <dd>{@code return false}</dd>
* </dl>
*
*
* @param pRequest the servlet request
* @return {@code true} if the filter should do image filtering
*/
protected boolean trigger(ServletRequest pRequest) {
// If triggerParams not set, assume always trigger
if (mTriggerParams == null) {
return true;
}
// Trigger only for certain request parameters
for (String triggerParam : mTriggerParams) {
if (pRequest.getParameter(triggerParam) != null) {
return true;
}
}
// Didn't trigger
return false;
}
/**
* Sets the trigger parameters.
* The parameter is supposed to be a comma-separated string of parameter
* names.
*
* @param pTriggerParams a comma-separated string of parameter names.
*/
public void setTriggerParams(String pTriggerParams) {
mTriggerParams = StringUtil.toStringArray(pTriggerParams);
}
/**
* Filters the image for this request.
*
* @param pImage the image to filter
* @param pRequest the servlet request
* @param pResponse the servlet response
*
* @return the filtered image
* @throws java.io.IOException if an I/O error occurs during filtering
*/
protected abstract RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException;
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import javax.servlet.*;
/**
* This excpetion is a subclass of ServletException, and acts just as a marker
* for excpetions thrown by the ImageServlet API.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java#2 $
*/
public class ImageServletException extends ServletException {
public ImageServletException(String pMessage) {
super(pMessage);
}
public ImageServletException(Throwable pThrowable) {
super(pThrowable);
}
public ImageServletException(String pMessage, Throwable pThrowable) {
super(pMessage, pThrowable);
}
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.awt.image.RenderedImage;
import java.awt.image.BufferedImage;
/**
* ImageServletResponse.
* <p/>
* The request attributes regarding image size and source region (AOI) are used
* in the decoding process, and must be set before the first invocation of
* {@link #getImage()} to have any effect.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java#4 $
*/
public interface ImageServletResponse extends ServletResponse {
/**
* Request attribute of type {@link java.awt.Dimension} controlling image
* size.
* If either {@code width} or {@code height} is negative, the size is
* computed, using uniform scaling.
* Else, if {@code SIZE_UNIFORM} is {@code true}, the size will be
* computed to the largest possible area (with correct aspect ratio)
* fitting inside the target area.
* Otherwise, the image is scaled to the given size, with no regard to
* aspect ratio.
* <p/>
* Defaults to {@code null} (original image size).
*/
String ATTRIB_SIZE = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE";
/**
* Request attribute of type {@link Boolean} controlling image sizing.
* <p/>
* Defaults to {@code Boolean.TRUE}.
*/
String ATTRIB_SIZE_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_UNIFORM";
/**
* Request attribute of type {@link Boolean} controlling image sizing.
* <p/>
* Defaults to {@code Boolean.FALSE}.
*/
String ATTRIB_SIZE_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_PERCENT";
/**
* Request attribute of type {@link java.awt.Rectangle} controlling image
* source region (area of interest).
* <p/>
* Defaults to {@code null} (the entire image).
*/
String ATTRIB_AOI = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI";
/**
* Request attribute of type {@link Boolean} controlling image AOI.
* <p/>
* Defaults to {@code Boolean.FALSE}.
*/
String ATTRIB_AOI_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_UNIFORM";
/**
* Request attribute of type {@link Boolean} controlling image AOI.
* <p/>
* Defaults to {@code Boolean.FALSE}.
*/
String ATTRIB_AOI_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_PERCENT";
/**
* Request attribute of type {@link java.awt.Color} controlling background
* color for any transparent/translucent areas of the image.
* <p/>
* Defaults to {@code null} (keeps the transparent areas transparent).
*/
String ATTRIB_BG_COLOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.BG_COLOR";
/**
* Request attribute of type {@link Float} controlling image output compression/quality.
* Used for formats that accepts compression or quality settings,
* like JPEG (quality), PNG (compression only) etc.
* <p/>
* Defaults to {@code 0.8f} for JPEG.
*/
String ATTRIB_OUTPUT_QUALITY = "com.twelvemonkeys.servlet.image.ImageServletResponse.OUTPUT_QUALITY";
/**
* Request attribute of type {@link Double} controlling image read
* subsampling factor. Controls the maximum sample pixels in each direction,
* that is read per pixel in the output image, if the result will be
* downscaled.
* Larger values will result in better quality, at the expense of higher
* memory consumption and CPU usage.
* However, using values above {@code 3.0} will usually not improve image
* quality.
* Legal values are in the range {@code [1.0 .. positive infinity&gt;}.
* <p/>
* Defaults to {@code 2.0}.
*/
String ATTRIB_READ_SUBSAMPLING_FACTOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.READ_SUBSAMPLING_FACTOR";
/**
* Request attribute of type {@link Integer} controlling image resample
* algorithm.
* Legal values are {@link java.awt.Image#SCALE_DEFAULT SCALE_DEFAULT},
* {@link java.awt.Image#SCALE_FAST SCALE_FAST} or
* {@link java.awt.Image#SCALE_SMOOTH SCALE_SMOOTH}.
* <p/>
* Note: When using a value of {@code SCALE_FAST}, you should also use a
* subsampling factor of {@code 1.0}, for fast read/scale.
* Otherwise, use a subsampling factor of {@code 2.0} for better quality.
* <p/>
* Defaults to {@code SCALE_DEFAULT}.
*/
String ATTRIB_IMAGE_RESAMPLE_ALGORITHM = "com.twelvemonkeys.servlet.image.ImageServletResponse.IMAGE_RESAMPLE_ALGORITHM";
/**
* Gets the image format for this response, such as "image/gif" or "image/jpeg".
* If not set, the default format is that of the original image.
*
* @return the image format for this response.
* @see #setOutputContentType(String)
*/
String getOutputContentType();
/**
* Sets the image format for this response, such as "image/gif" or "image/jpeg".
* <p/>
* As an example, a custom filter could do content negotiation based on the
* request header fields and write the image back in an appropriate format.
* <p/>
* If not set, the default format is that of the original image.
*
* @param pImageFormat the image format for this response.
*/
void setOutputContentType(String pImageFormat);
//TODO: ?? void setCompressionQuality(float pQualityFactor);
//TODO: ?? float getCompressionQuality();
/**
* Writes the image to the original {@code ServletOutputStream}.
* If no format is {@linkplain #setOutputContentType(String) set} in this response,
* the image is encoded in the same format as the original image.
*
* @throws java.io.IOException if an I/O exception occurs during writing
*/
void flush() throws IOException;
/**
* Gets the decoded image from the response.
*
* @return a {@code BufferedImage} or {@code null} if the image could not be read.
*
* @throws java.io.IOException if an I/O exception occurs during reading
*/
BufferedImage getImage() throws IOException;
/**
* Sets the image for this response.
*
* @param pImage the new response image.
*/
void setImage(RenderedImage pImage);
}

View File

@@ -0,0 +1,735 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.imageio.*;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Iterator;
/**
* This {@link ImageServletResponse} implementation can be used with image
* requests, to have the image immediately decoded to a {@code BufferedImage}.
* The image may be optionally subsampled, scaled and/or cropped.
* The response also automtically handles writing the image back to the underlying response stream
* in the preferred format, when the response is flushed.
* <p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java#10 $
*
*/
// TODO: Refactor out HTTP specifcs (if possible).
// TODO: Is it a good ide to throw IIOException?
class ImageServletResponseImpl extends HttpServletResponseWrapper implements ImageServletResponse {
private final ServletRequest mOriginalRequest;
private final ServletContext mContext;
private final ServletResponseStreamDelegate mStreamDelegate;
private FastByteArrayOutputStream mBufferedOut;
private RenderedImage mImage;
private String mOutputContentType;
private String mOriginalContentType;
private int mOriginalContentLength = -1;
/**
* Creates an {@code ImageServletResponseImpl}.
*
* @param pRequest the request
* @param pResponse the response
* @param pContext the servlet context
*/
public ImageServletResponseImpl(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final ServletContext pContext) {
super(pResponse);
mOriginalRequest = pRequest;
mStreamDelegate = new ServletResponseStreamDelegate(pResponse) {
@Override
protected OutputStream createOutputStream() throws IOException {
if (mOriginalContentLength >= 0) {
mBufferedOut = new FastByteArrayOutputStream(mOriginalContentLength);
}
else {
mBufferedOut = new FastByteArrayOutputStream(0);
}
return mBufferedOut;
}
};
mContext = pContext;
}
/**
* Creates an {@code ImageServletResponseImpl}.
*
* @param pRequest the request
* @param pResponse the response
* @param pContext the servlet context
*
* @throws ClassCastException if {@code pRequest} is not an {@link javax.servlet.http.HttpServletRequest} or
* {@code pResponse} is not an {@link javax.servlet.http.HttpServletResponse}.
*/
public ImageServletResponseImpl(final ServletRequest pRequest, final ServletResponse pResponse, final ServletContext pContext) {
// Cheat for now...
this((HttpServletRequest) pRequest, (HttpServletResponse) pResponse, pContext);
}
/**
* Called by the container, do not invoke.
*
* @param pMimeType the content (MIME) type
*/
public void setContentType(final String pMimeType) {
// Throw exception is already set
if (mOriginalContentType != null) {
throw new IllegalStateException("ContentType already set.");
}
mOriginalContentType = pMimeType;
}
/**
* Called by the container. Do not invoke.
*
* @return the response's {@code OutputStream}
* @throws IOException
*/
public ServletOutputStream getOutputStream() throws IOException {
return mStreamDelegate.getOutputStream();
}
/**
* Called by the container. Do not invoke.
*
* @return the response's {@code PrintWriter}
* @throws IOException
*/
public PrintWriter getWriter() throws IOException {
return mStreamDelegate.getWriter();
}
/**
* Called by the container. Do not invoke.
*
* @param pLength the content length
*/
public void setContentLength(final int pLength) {
if (mOriginalContentLength != -1) {
throw new IllegalStateException("ContentLength already set.");
}
mOriginalContentLength = pLength;
}
/**
* Writes the image to the original {@code ServletOutputStream}.
* If no format is set in this response, the image is encoded in the same
* format as the original image.
*
* @throws IOException if an I/O exception occurs during writing
*/
public void flush() throws IOException {
String outputType = getOutputContentType();
// Force transcoding, if no other filtering is done
if (!outputType.equals(mOriginalContentType)) {
getImage();
}
// For known formats that don't support transparency, convert to opaque
if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType)
|| "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) &&
mImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB);
}
if (mImage != null) {
Iterator writers = ImageIO.getImageWritersByMIMEType(outputType);
if (writers.hasNext()) {
super.setContentType(outputType);
OutputStream out = super.getOutputStream();
ImageWriter writer = (ImageWriter) writers.next();
try {
ImageWriteParam param = writer.getDefaultWriteParam();
Float requestQuality = (Float) mOriginalRequest.getAttribute(ImageServletResponse.ATTRIB_OUTPUT_QUALITY);
// The default JPEG quality is not good enough, so always apply compression
if ((requestQuality != null || "jpeg".equalsIgnoreCase(getFormatNameSafe(writer))) && param.canWriteCompressed()) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(requestQuality != null ? requestQuality : 0.8f);
}
ImageOutputStream stream = ImageIO.createImageOutputStream(out);
writer.setOutput(stream);
try {
writer.write(null, new IIOImage(mImage, null, null), param);
}
finally {
stream.close();
}
}
finally {
writer.dispose();
out.flush();
}
}
else {
mContext.log("ERROR: No writer for content-type: " + outputType);
throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ").");
}
}
else {
super.setContentType(mOriginalContentType);
ServletOutputStream out = super.getOutputStream();
try {
mBufferedOut.writeTo(out);
}
finally {
out.flush();
}
}
}
private String getFormatNameSafe(final ImageWriter pWriter) {
try {
return pWriter.getOriginatingProvider().getFormatNames()[0];
}
catch (RuntimeException e) {
// NPE, AIOOBE, etc..
return null;
}
}
public String getOutputContentType() {
return mOutputContentType != null ? mOutputContentType : mOriginalContentType;
}
public void setOutputContentType(final String pImageFormat) {
mOutputContentType = pImageFormat;
}
/**
* Sets the image for this response.
*
* @param pImage the {@code RenderedImage} that will be written to the
* response stream
*/
public void setImage(final RenderedImage pImage) {
mImage = pImage;
}
/**
* Gets the decoded image from the response.
*
* @return a {@code BufferedImage} or {@code null} if the image could
* not be read.
*
* @throws java.io.IOException if an I/O exception occurs during reading
*/
public BufferedImage getImage() throws IOException {
if (mImage == null) {
// No content, no image
if (mBufferedOut == null) {
return null;
}
// Read from the byte buffer
InputStream byteStream = mBufferedOut.createInputStream();
ImageInputStream input = null;
try {
input = ImageIO.createImageInputStream(byteStream);
Iterator readers = ImageIO.getImageReaders(input);
if (readers.hasNext()) {
// Get the correct reader
ImageReader reader = (ImageReader) readers.next();
try {
reader.setInput(input);
ImageReadParam param = reader.getDefaultReadParam();
// Get default size
int originalWidth = reader.getWidth(0);
int originalHeight = reader.getHeight(0);
// Extract AOI from request
Rectangle aoi = extractAOIFromRequest(originalWidth, originalHeight);
if (aoi != null) {
param.setSourceRegion(aoi);
originalWidth = aoi.width;
originalHeight = aoi.height;
}
// If possible, extract size from request
Dimension size = extractSizeFromRequest(originalWidth, originalHeight);
double readSubSamplingFactor = getReadSubsampleFactorFromRequest();
if (size != null) {
//System.out.println("Size: " + size);
if (param.canSetSourceRenderSize()) {
param.setSourceRenderSize(size);
}
else {
int subX = (int) Math.max(originalWidth / (double) (size.width * readSubSamplingFactor), 1.0);
int subY = (int) Math.max(originalHeight / (double) (size.height * readSubSamplingFactor), 1.0);
if (subX > 1 || subY > 1) {
param.setSourceSubsampling(subX, subY, subX > 1 ? subX / 2 : 0, subY > 1 ? subY / 2 : 0);
}
}
}
// Need base URI for SVG with links/stylesheets etc
maybeSetBaseURIFromRequest(param);
// Finally, read the image using the supplied parameter
BufferedImage image = reader.read(0, param);
// If reader doesn't support dynamic sizing, scale now
if (image != null && size != null
&& (image.getWidth() != size.width || image.getHeight() != size.height)) {
int resampleAlgorithm = getResampleAlgorithmFromRequest();
// NOTE: Only use createScaled if IndexColorModel,
// as it's more expensive due to color conversion
if (image.getColorModel() instanceof IndexColorModel) {
image = ImageUtil.createScaled(image, size.width, size.height, resampleAlgorithm);
}
else {
image = ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm);
}
}
// Fill bgcolor behind image, if transparent
extractAndSetBackgroundColor(image);
// Set image
mImage = image;
}
finally {
reader.dispose();
}
}
else {
mContext.log("ERROR: No suitable image reader found (content-type: " + mOriginalContentType + ").");
mContext.log("ERROR: Available formats: " + getFormatsString());
throw new IIOException("Unable to transcode image: No suitable image reader found (content-type: " + mOriginalContentType + ").");
}
// Free resources, as the image is now either read, or unreadable
mBufferedOut = null;
}
finally {
if (input != null) {
input.close();
}
}
}
// Image is usually a BufferedImage, but may also be a RenderedImage
return mImage != null ? ImageUtil.toBuffered(mImage) : null;
}
private int getResampleAlgorithmFromRequest() {
int resampleAlgoithm;
Object algorithm = mOriginalRequest.getAttribute(ATTRIB_IMAGE_RESAMPLE_ALGORITHM);
if (algorithm instanceof Integer && ((Integer) algorithm == Image.SCALE_SMOOTH || (Integer) algorithm == Image.SCALE_FAST || (Integer) algorithm == Image.SCALE_DEFAULT)) {
resampleAlgoithm = (Integer) algorithm;
}
else {
if (algorithm != null) {
mContext.log("WARN: Illegal image resampling algorithm: " + algorithm);
}
resampleAlgoithm = BufferedImage.SCALE_DEFAULT;
}
return resampleAlgoithm;
}
private double getReadSubsampleFactorFromRequest() {
double subsampleFactor;
Object factor = mOriginalRequest.getAttribute(ATTRIB_READ_SUBSAMPLING_FACTOR);
if (factor instanceof Number && ((Number) factor).doubleValue() >= 1.0) {
subsampleFactor = ((Number) factor).doubleValue();
}
else {
if (factor != null) {
mContext.log("WARN: Illegal read subsampling factor: " + factor);
}
subsampleFactor = 2.0;
}
return subsampleFactor;
}
private void extractAndSetBackgroundColor(final BufferedImage pImage) {
// TODO: bgColor request attribute instead of parameter?
if (pImage.getColorModel().hasAlpha()) {
String bgColor = mOriginalRequest.getParameter("bg.color");
if (bgColor != null) {
Color color = StringUtil.toColor(bgColor);
Graphics2D g = pImage.createGraphics();
try {
g.setColor(color);
g.setComposite(AlphaComposite.DstOver);
g.fillRect(0, 0, pImage.getWidth(), pImage.getHeight());
}
finally {
g.dispose();
}
}
}
}
private static String getFormatsString() {
String[] formats = ImageIO.getReaderFormatNames();
StringBuilder buf = new StringBuilder();
for (int i = 0; i < formats.length; i++) {
String format = formats[i];
if (i > 0) {
buf.append(", ");
}
buf.append(format);
}
return buf.toString();
}
private void maybeSetBaseURIFromRequest(final ImageReadParam pParam) {
if (mOriginalRequest instanceof HttpServletRequest) {
try {
// If there's a setBaseURI method, we'll try to use that (uses reflection, to avoid dependency on plugins)
Method setBaseURI;
try {
setBaseURI = pParam.getClass().getMethod("setBaseURI", String.class);
}
catch (NoSuchMethodException ignore) {
return;
}
// Get URL for resource and set as base
String baseURI = ServletUtil.getContextRelativeURI((HttpServletRequest) mOriginalRequest);
URL resourceURL = mContext.getResource(baseURI);
if (resourceURL == null) {
resourceURL = ServletUtil.getRealURL(mContext, baseURI);
}
if (resourceURL != null) {
setBaseURI.invoke(pParam, resourceURL.toExternalForm());
}
else {
mContext.log("WARN: Resource URL not found for URI: " + baseURI);
}
}
catch (Exception e) {
mContext.log("WARN: Could not set base URI: ", e);
}
}
}
private Dimension extractSizeFromRequest(final int pDefaultWidth, final int pDefaultHeight) {
// TODO: Allow extraction from request parameters
/*
int sizeW = ServletUtil.getIntParameter(mOriginalRequest, "size.w", -1);
int sizeH = ServletUtil.getIntParameter(mOriginalRequest, "size.h", -1);
boolean sizePercent = ServletUtil.getBooleanParameter(mOriginalRequest, "size.percent", false);
boolean sizeUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "size.uniform", true);
*/
Dimension size = (Dimension) mOriginalRequest.getAttribute(ATTRIB_SIZE);
int sizeW = size != null ? size.width : -1;
int sizeH = size != null ? size.height : -1;
Boolean b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_SIZE_PERCENT);
boolean sizePercent = b != null && b; // default: false
b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_SIZE_UNIFORM);
boolean sizeUniform = b == null || b; // default: true
if (sizeW >= 0 || sizeH >= 0) {
size = getSize(pDefaultWidth, pDefaultHeight, sizeW, sizeH, sizePercent, sizeUniform);
}
return size;
}
private Rectangle extractAOIFromRequest(final int pDefaultWidth, final int pDefaultHeight) {
// TODO: Allow extraction from request parameters
/*
int aoiX = ServletUtil.getIntParameter(mOriginalRequest, "aoi.x", -1);
int aoiY = ServletUtil.getIntParameter(mOriginalRequest, "aoi.y", -1);
int aoiW = ServletUtil.getIntParameter(mOriginalRequest, "aoi.w", -1);
int aoiH = ServletUtil.getIntParameter(mOriginalRequest, "aoi.h", -1);
boolean aoiPercent = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.percent", false);
boolean aoiUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.uniform", false);
*/
Rectangle aoi = (Rectangle) mOriginalRequest.getAttribute(ATTRIB_AOI);
int aoiX = aoi != null ? aoi.x : -1;
int aoiY = aoi != null ? aoi.y : -1;
int aoiW = aoi != null ? aoi.width : -1;
int aoiH = aoi != null ? aoi.height : -1;
Boolean b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_AOI_PERCENT);
boolean aoiPercent = b != null && b; // default: false
b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_AOI_UNIFORM);
boolean aoiUniform = b != null && b; // default: false
if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) {
aoi = getAOI(pDefaultWidth, pDefaultHeight, aoiX, aoiY, aoiW, aoiH, aoiPercent, aoiUniform);
return aoi;
}
return null;
}
// TODO: Move these to ImageUtil or similar, as they are often used...
// TODO: Consider separate methods for percent and pixels
/**
* Gets the dimensions (height and width) of the scaled image. The
* dimensions are computed based on the old image's dimensions, the units
* used for specifying new dimensions and whether or not uniform scaling
* should be used (se algorithm below).
*
* @param pOriginalWidth the original width of the image
* @param pOriginalHeight the original height of the image
* @param pWidth the new width of the image, or -1 if unknown
* @param pHeight the new height of the image, or -1 if unknown
* @param pPercent the constant specifying units for width and height
* parameter (UNITS_PIXELS or UNITS_PERCENT)
* @param pUniformScale boolean specifying uniform scale or not
* @return a Dimension object, with the correct width and heigth
* in pixels, for the scaled version of the image.
*/
protected static Dimension getSize(int pOriginalWidth, int pOriginalHeight,
int pWidth, int pHeight,
boolean pPercent, boolean pUniformScale) {
// If uniform, make sure width and height are scaled the same ammount
// (use ONLY height or ONLY width).
//
// Algoritm:
// if uniform
// if newHeight not set
// find ratio newWidth / oldWidth
// oldHeight *= ratio
// else if newWidth not set
// find ratio newWidth / oldWidth
// oldHeight *= ratio
// else
// find both ratios and use the smallest one
// (this will be the largest version of the image that fits
// inside the rectangle given)
// (if PERCENT, just use smallest percentage).
//
// If units is percent, we only need old height and width
float ratio;
if (pPercent) {
if (pWidth >= 0 && pHeight >= 0) {
// Non-uniform
pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / 100f;
pWidth = Math.round((float) pOriginalWidth * ratio);
pHeight = Math.round((float) pOriginalHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / 100f;
pWidth = Math.round((float) pOriginalWidth * ratio);
pHeight = Math.round((float) pOriginalHeight * ratio);
}
// Else: No scale
}
else {
if (pUniformScale) {
if (pWidth >= 0 && pHeight >= 0) {
// Compute both ratios
ratio = (float) pWidth / (float) pOriginalWidth;
float heightRatio = (float) pHeight / (float) pOriginalHeight;
// Find the largest ratio, and use that for both
if (heightRatio < ratio) {
ratio = heightRatio;
pWidth = Math.round((float) pOriginalWidth * ratio);
}
else {
pHeight = Math.round((float) pOriginalHeight * ratio);
}
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / (float) pOriginalWidth;
pHeight = Math.round((float) pOriginalHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / (float) pOriginalHeight;
pWidth = Math.round((float) pOriginalWidth * ratio);
}
// Else: No scale
}
}
// Default is no scale, just work as a proxy
if (pWidth < 0) {
pWidth = pOriginalWidth;
}
if (pHeight < 0) {
pHeight = pOriginalHeight;
}
// Create new Dimension object and return
return new Dimension(pWidth, pHeight);
}
protected static Rectangle getAOI(int pOriginalWidth, int pOriginalHeight,
int pX, int pY, int pWidth, int pHeight,
boolean pPercent, boolean pUniform) {
// Algoritm:
// Try to get x and y (default 0,0).
// Try to get width and height (default width-x, height-y)
//
// If percent, get ratio
//
// If uniform
//
float ratio;
if (pPercent) {
if (pWidth >= 0 && pHeight >= 0) {
// Non-uniform
pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / 100f;
pWidth = Math.round((float) pOriginalWidth * ratio);
pHeight = Math.round((float) pOriginalHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / 100f;
pWidth = Math.round((float) pOriginalWidth * ratio);
pHeight = Math.round((float) pOriginalHeight * ratio);
}
// Else: No crop
}
else {
// Uniform
if (pUniform) {
if (pWidth >= 0 && pHeight >= 0) {
// Compute both ratios
ratio = (float) pWidth / (float) pHeight;
float originalRatio = (float) pOriginalWidth / (float) pOriginalHeight;
if (ratio > originalRatio) {
pWidth = pOriginalWidth;
pHeight = Math.round((float) pOriginalWidth / ratio);
}
else {
pHeight = pOriginalHeight;
pWidth = Math.round((float) pOriginalHeight * ratio);
}
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / (float) pOriginalWidth;
pHeight = Math.round((float) pOriginalHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / (float) pOriginalHeight;
pWidth = Math.round((float) pOriginalWidth * ratio);
}
// Else: No crop
}
}
// Not specified, or outside bounds: Use original dimensions
if (pWidth < 0 || (pX < 0 && pWidth > pOriginalWidth)
|| (pX >= 0 && (pX + pWidth) > pOriginalWidth)) {
pWidth = (pX >= 0 ? pOriginalWidth - pX : pOriginalWidth);
}
if (pHeight < 0 || (pY < 0 && pHeight > pOriginalHeight)
|| (pY >= 0 && (pY + pHeight) > pOriginalHeight)) {
pHeight = (pY >= 0 ? pOriginalHeight - pY : pOriginalHeight);
}
// Center
if (pX < 0) {
pX = (pOriginalWidth - pWidth) / 2;
}
if (pY < 0) {
pY = (pOriginalHeight - pHeight) / 2;
}
// System.out.println("x: " + pX + " y: " + pY
// + " w: " + pWidth + " h " + pHeight);
return new Rectangle(pX, pY, pWidth, pHeight);
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import javax.servlet.ServletRequest;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
/**
* An {@code ImageFilter} that does nothing. Useful for debugging purposes.
*
* @author $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java#2 $
*
*/
public final class NullImageFilter extends ImageFilter {
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
return pImage;
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.lang.MathUtil;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.ServletRequest;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
/**
* This Servlet is able to render a cropped part of an image.
*
* <P><HR><P>
*
* <A name="parameters"></A><STRONG>Parameters:</STRONG><BR>
* <DL>
* <DT>{@code cropX}</DT>
* <DD>integer, the new left edge of the image.
* <DT>{@code cropY}</DT>
* <DD>integer, the new top of the image.
* <DT>{@code cropWidth}</DT>
* <DD>integer, the new width of the image.
* <DT>{@code cropHeight}</DT>
* <DD>integer, the new height of the image.
* <!--
* <DT>{@code cropUniform}</DT>
* <DD>boolean, wether or not uniform scalnig should be used. Default is
* {@code true}.
* <DT>{@code cropUnits}</DT>
* <DD>string, one of {@code PIXELS}, {@code PERCENT}.
* {@code PIXELS} is default. -->
*
*
* </DL>
*
* @example
* JPEG:
* &lt;IMG src="/scale/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=500&uniform=true"&gt;
*
* PNG:
* &lt;IMG src="/scale/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=50&units=PERCENT"&gt;
*
* @todo Correct rounding errors, resulting in black borders when rotating 90
* degrees, and one of width or height is odd length...
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java#1 $
*/
public class RotateFilter extends ImageFilter {
/** {@code angle}*/
protected final static String PARAM_ANGLE = "angle";
/** {@code angleUnits (RADIANS|DEGREES)}*/
protected final static String PARAM_ANGLE_UNITS = "angleUnits";
/** {@code crop}*/
protected final static String PARAM_CROP = "rotateCrop";
/** {@code bgcolor}*/
protected final static String PARAM_BGCOLOR = "rotateBgcolor";
/** {@code degrees}*/
private final static String ANGLE_DEGREES = "degrees";
/** {@code radians}*/
//private final static String ANGLE_RADIANS = "radians";
/**
* Reads the image from the requested URL, rotates it, and returns
* it in the
* Servlet stream. See above for details on parameters.
*/
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
// Get angle
double ang = getAngle(pRequest);
// Get bounds
Rectangle2D rect = getBounds(pRequest, pImage, ang);
int width = (int) rect.getWidth();
int height = (int) rect.getHeight();
// Create result image
BufferedImage res = ImageUtil.createTransparent(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = res.createGraphics();
// Get background color and clear
String str = pRequest.getParameter(PARAM_BGCOLOR);
if (!StringUtil.isEmpty(str)) {
Color bgcolor = StringUtil.toColor(str);
g.setBackground(bgcolor);
g.clearRect(0, 0, width, height);
}
// Set mHints (why do I always get jagged edgdes?)
RenderingHints hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
hints.add(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
g.setRenderingHints(hints);
// Rotate around center
AffineTransform at = AffineTransform
.getRotateInstance(ang, width / 2.0, height / 2.0);
// Move to center
at.translate(width / 2.0 - pImage.getWidth() / 2.0,
height / 2.0 - pImage.getHeight() / 2.0);
// Draw it, centered
g.drawImage(pImage, at, null);
return res;
}
/**
* Gets the angle of rotation.
*/
private double getAngle(ServletRequest pReq) {
double angle = 0.0;
String str = pReq.getParameter(PARAM_ANGLE);
if (!StringUtil.isEmpty(str)) {
angle = Double.parseDouble(str);
// Convert to radians, if needed
str = pReq.getParameter(PARAM_ANGLE_UNITS);
if (!StringUtil.isEmpty(str)
&& ANGLE_DEGREES.equalsIgnoreCase(str)) {
angle = MathUtil.toRadians(angle);
}
}
return angle;
}
/**
* Get the bounding rectangle of the rotated image.
*/
private Rectangle2D getBounds(ServletRequest pReq, BufferedImage pImage,
double pAng) {
// Get dimensions of original image
int width = pImage.getWidth(); // loads the image
int height = pImage.getHeight();
// Test if we want to crop image (default)
// if true
// - find the largest bounding box INSIDE the rotated image,
// that matches the original proportions (nearest 90deg)
// (scale up to fit dimensions?)
// else
// - find the smallest bounding box OUTSIDE the rotated image.
// - that matches the original proportions (nearest 90deg) ?
// (scale down to fit dimensions?)
AffineTransform at =
AffineTransform.getRotateInstance(pAng, width / 2.0, height / 2.0);
Rectangle2D orig = new Rectangle(width, height);
Shape rotated = at.createTransformedShape(orig);
if (ServletUtil.getBooleanParameter(pReq, PARAM_CROP, false)) {
// TODO: Inside box
return rotated.getBounds2D();
}
else {
return rotated.getBounds2D();
}
}
}

View File

@@ -0,0 +1,322 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.ServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.lang.reflect.Field;
/**
* This filter renders a scaled version of an image read from a
* given URL. The image can be output as a GIF, JPEG or PNG image
* or similar<!--,
* with optional caching of the rendered image files-->.
* <P>
* <P><HR><P>
* <p/>
* <A name="parameters"></A><STRONG>Parameters:</STRONG><BR>
* <DL>
* <DT>{@code scaleX}</DT>
* <DD>integer, the new width of the image.
* <DT>{@code scaleY}</DT>
* <DD>integer, the new height of the image.
* <DT>{@code scaleUniform}</DT>
* <DD>boolean, wether or not uniform scalnig should be used. Default is
* {@code true}.
* <DT>{@code scaleUnits}</DT>
* <DD>string, one of {@code PIXELS}, {@code PERCENT}.
* {@code PIXELS} is default.
* <DT>{@code scaleQuality}</DT>
* <DD>string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
* {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
* {@code SCALE_DEFAULT} is default (see
* {@link java.awt.Image#getScaledInstance(int,int,int)}, {@link java.awt.Image}
* for more details).
* </DL>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java#1 $
*
* @example &lt;IMG src="/scale/test.jpg?scaleX=500&scaleUniform=false"&gt;
* @example &lt;IMG src="/scale/test.png?scaleY=50&scaleUnits=PERCENT"&gt;
*/
public class ScaleFilter extends ImageFilter {
/**
* Width and height are absolute pixels. The default.
*/
public static final int UNITS_PIXELS = 1;
/**
* Width and height are percentage of original width and height.
*/
public static final int UNITS_PERCENT = 5;
/**
* Ahh, good choice!
*/
//private static final int UNITS_METRIC = 42;
/**
* The root of all evil...
*/
//private static final int UNITS_INCHES = 666;
/**
* Unknown units. <!-- Oops, what now? -->
*/
public static final int UNITS_UNKNOWN = 0;
/**
* {@code scaleQuality}
*/
protected final static String PARAM_SCALE_QUALITY = "scaleQuality";
/**
* {@code scaleUnits}
*/
protected final static String PARAM_SCALE_UNITS = "scaleUnits";
/**
* {@code scaleUniform}
*/
protected final static String PARAM_SCALE_UNIFORM = "scaleUniform";
/**
* {@code scaleX}
*/
protected final static String PARAM_SCALE_X = "scaleX";
/**
* {@code scaleY}
*/
protected final static String PARAM_SCALE_Y = "scaleY";
/**
* {@code image}
*/
protected final static String PARAM_IMAGE = "image";
/** */
protected int mDefaultScaleQuality = Image.SCALE_DEFAULT;
/**
* Reads the image from the requested URL, scales it, and returns it in the
* Servlet stream. See above for details on parameters.
*/
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
// Get quality setting
// SMOOTH | FAST | REPLICATE | DEFAULT | AREA_AVERAGING
// See Image (mHints)
int quality = getQuality(pRequest.getParameter(PARAM_SCALE_QUALITY));
// Get units, default is pixels
// PIXELS | PERCENT | METRIC | INCHES
int units = getUnits(pRequest.getParameter(PARAM_SCALE_UNITS));
if (units == UNITS_UNKNOWN) {
log("Unknown units for scale, returning original.");
return pImage;
}
// Use uniform scaling? Default is true
boolean uniformScale = ServletUtil.getBooleanParameter(pRequest, PARAM_SCALE_UNIFORM, true);
// Get dimensions
int width = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_X, -1);
int height = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_Y, -1);
// Get dimensions for scaled image
Dimension dim = getDimensions(pImage, width, height, units, uniformScale);
width = (int) dim.getWidth();
height = (int) dim.getHeight();
// Return scaled instance directly
return ImageUtil.createScaled(pImage, width, height, quality);
}
/**
* Gets the quality constant for the scaling, from the string argument.
*
* @param pQualityStr The string representation of the scale quality
* constant.
* @return The matching quality constant, or the default quality if none
* was found.
* @see java.awt.Image
* @see java.awt.Image#getScaledInstance(int,int,int)
*/
protected int getQuality(String pQualityStr) {
if (!StringUtil.isEmpty(pQualityStr)) {
try {
// Get quality constant from Image using reflection
Class cl = Image.class;
Field field = cl.getField(pQualityStr.toUpperCase());
return field.getInt(null);
}
catch (IllegalAccessException ia) {
log("Unable to get quality.", ia);
}
catch (NoSuchFieldException nsf) {
log("Unable to get quality.", nsf);
}
}
return mDefaultScaleQuality;
}
public void setDefaultScaleQuality(String pDefaultScaleQuality) {
mDefaultScaleQuality = getQuality(pDefaultScaleQuality);
}
/**
* Gets the units constant for the width and height arguments, from the
* given string argument.
*
* @param pUnitStr The string representation of the units constant,
* can be one of "PIXELS" or "PERCENT".
* @return The mathcing units constant, or UNITS_UNKNOWN if none was found.
*/
protected int getUnits(String pUnitStr) {
if (StringUtil.isEmpty(pUnitStr)
|| pUnitStr.equalsIgnoreCase("PIXELS")) {
return UNITS_PIXELS;
}
else if (pUnitStr.equalsIgnoreCase("PERCENT")) {
return UNITS_PERCENT;
}
else {
return UNITS_UNKNOWN;
}
}
/**
* Gets the dimensions (height and width) of the scaled image. The
* dimensions are computed based on the old image's dimensions, the units
* used for specifying new dimensions and whether or not uniform scaling
* should be used (se algorithm below).
*
* @param pImage the image to be scaled
* @param pWidth the new width of the image, or -1 if unknown
* @param pHeight the new height of the image, or -1 if unknown
* @param pUnits the constant specifying units for width and height
* parameter (UNITS_PIXELS or UNITS_PERCENT)
* @param pUniformScale boolean specifying uniform scale or not
* @return a Dimension object, with the correct width and heigth
* in pixels, for the scaled version of the image.
*/
protected Dimension getDimensions(Image pImage, int pWidth, int pHeight,
int pUnits, boolean pUniformScale) {
// If uniform, make sure width and height are scaled the same ammount
// (use ONLY height or ONLY width).
//
// Algoritm:
// if uniform
// if newHeight not set
// find ratio newWidth / oldWidth
// oldHeight *= ratio
// else if newWidth not set
// find ratio newWidth / oldWidth
// oldHeight *= ratio
// else
// find both ratios and use the smallest one
// (this will be the largest version of the image that fits
// inside the rectangle given)
// (if PERCENT, just use smallest percentage).
//
// If units is percent, we only need old height and width
int oldWidth = ImageUtil.getWidth(pImage);
int oldHeight = ImageUtil.getHeight(pImage);
float ratio;
if (pUnits == UNITS_PERCENT) {
if (pWidth >= 0 && pHeight >= 0) {
// Non-uniform
pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / 100f;
pWidth = (int) ((float) oldWidth * ratio);
pHeight = (int) ((float) oldHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / 100f;
pWidth = (int) ((float) oldWidth * ratio);
pHeight = (int) ((float) oldHeight * ratio);
}
// Else: No scale
}
else if (pUnits == UNITS_PIXELS) {
if (pUniformScale) {
if (pWidth >= 0 && pHeight >= 0) {
// Compute both ratios
ratio = (float) pWidth / (float) oldWidth;
float heightRatio = (float) pHeight / (float) oldHeight;
// Find the largest ratio, and use that for both
if (heightRatio < ratio) {
ratio = heightRatio;
pWidth = (int) ((float) oldWidth * ratio);
}
else {
pHeight = (int) ((float) oldHeight * ratio);
}
}
else if (pWidth >= 0) {
// Find ratio from pWidth
ratio = (float) pWidth / (float) oldWidth;
pHeight = (int) ((float) oldHeight * ratio);
}
else if (pHeight >= 0) {
// Find ratio from pHeight
ratio = (float) pHeight / (float) oldHeight;
pWidth = (int) ((float) oldWidth * ratio);
}
// Else: No scale
}
}
// Default is no scale, just work as a proxy
if (pWidth < 0) {
pWidth = oldWidth;
}
if (pHeight < 0) {
pHeight = oldHeight;
}
// Create new Dimension object and return
return new Dimension(pWidth, pHeight);
}
}

View File

@@ -0,0 +1,154 @@
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import java.awt.image.RenderedImage;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.io.IOException;
/**
* A {@link javax.servlet.Filter} that extracts request parameters, and sets the
* corresponding request attributes from {@link ImageServletResponse}.
* Only affects how the image is decoded, and must be applied before any
* other image filters in the chain.
* <p/>
* @see ImageServletResponse#ATTRIB_SIZE
* @see ImageServletResponse#ATTRIB_AOI
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java#1 $
*/
public class SourceRenderFilter extends ImageFilter {
private String mSizeWidthParam = "size.w";
private String mSizeHeightParam = "size.h";
private String mSizePercentParam = "size.percent";
private String mSizeUniformParam = "size.uniform";
private String mRegionWidthParam = "aoi.w";
private String mRegionHeightParam = "aoi.h";
private String mRegionLeftParam = "aoi.x";
private String mRegionTopParam = "aoi.y";
private String mRegionPercentParam = "aoi.percent";
private String mRegionUniformParam = "aoi.uniform";
public void setRegionHeightParam(String pRegionHeightParam) {
mRegionHeightParam = pRegionHeightParam;
}
public void setRegionWidthParam(String pRegionWidthParam) {
mRegionWidthParam = pRegionWidthParam;
}
public void setRegionLeftParam(String pRegionLeftParam) {
mRegionLeftParam = pRegionLeftParam;
}
public void setRegionTopParam(String pRegionTopParam) {
mRegionTopParam = pRegionTopParam;
}
public void setSizeHeightParam(String pSizeHeightParam) {
mSizeHeightParam = pSizeHeightParam;
}
public void setSizeWidthParam(String pSizeWidthParam) {
mSizeWidthParam = pSizeWidthParam;
}
public void setRegionPercentParam(String pRegionPercentParam) {
mRegionPercentParam = pRegionPercentParam;
}
public void setRegionUniformParam(String pRegionUniformParam) {
mRegionUniformParam = pRegionUniformParam;
}
public void setSizePercentParam(String pSizePercentParam) {
mSizePercentParam = pSizePercentParam;
}
public void setSizeUniformParam(String pSizeUniformParam) {
mSizeUniformParam = pSizeUniformParam;
}
public void init() throws ServletException {
if (mTriggerParams == null) {
// Add all params as triggers
mTriggerParams = new String[]{mSizeWidthParam, mSizeHeightParam,
mSizeUniformParam, mSizePercentParam,
mRegionLeftParam, mRegionTopParam,
mRegionWidthParam, mRegionHeightParam,
mRegionUniformParam, mRegionPercentParam};
}
}
/**
* Extracts request parameters, and sets the corresponding request
* attributes if specified.
*
* @param pRequest
* @param pResponse
* @param pChain
* @throws IOException
* @throws ServletException
*/
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
// TODO: Max size configuration, to avoid DOS attacks? OutOfMemory
// Size parameters
int width = ServletUtil.getIntParameter(pRequest, mSizeWidthParam, -1);
int height = ServletUtil.getIntParameter(pRequest, mSizeHeightParam, -1);
if (width > 0 || height > 0) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE, new Dimension(width, height));
}
// Size uniform/percent
boolean uniform = ServletUtil.getBooleanParameter(pRequest, mSizeUniformParam, true);
if (!uniform) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.FALSE);
}
boolean percent = ServletUtil.getBooleanParameter(pRequest, mSizePercentParam, false);
if (percent) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
}
// Area of interest parameters
int x = ServletUtil.getIntParameter(pRequest, mRegionLeftParam, -1); // Default is center
int y = ServletUtil.getIntParameter(pRequest, mRegionTopParam, -1); // Default is center
width = ServletUtil.getIntParameter(pRequest, mRegionWidthParam, -1);
height = ServletUtil.getIntParameter(pRequest, mRegionHeightParam, -1);
if (width > 0 || height > 0) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_AOI, new Rectangle(x, y, width, height));
}
// AOI uniform/percent
uniform = ServletUtil.getBooleanParameter(pRequest, mRegionUniformParam, false);
if (uniform) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.TRUE);
}
percent = ServletUtil.getBooleanParameter(pRequest, mRegionPercentParam, false);
if (percent) {
pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
}
super.doFilterImpl(pRequest, pResponse, pChain);
}
/**
* This implementation does no filtering, and simply returns the image
* passed in.
*
* @param pImage
* @param pRequest
* @param pResponse
* @return {@code pImage}
*/
protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
return pImage;
}
}

View File

@@ -0,0 +1,348 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS 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 COPYRIGHT OWNER OR
* 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.
*/
package com.twelvemonkeys.servlet.image;
import com.twelvemonkeys.lang.MathUtil;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.ServletUtil;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.geom.Rectangle2D;
/**
* This servlet is capable of rendereing a text string and output it as an
* image. The text can be rendered in any given font, size,
* style or color, into an image, and output it as a GIF, JPEG or PNG image,
* with optional caching of the rendered image files.
*
* <P><HR><P>
*
* <A name="parameters"></A><STRONG>Parameters:</STRONG><BR>
* <DL>
* <DT>{@code text}</DT>
* <DD>string, the text string to render.
* <DT>{@code width}</DT>
* <DD>integer, the width of the image
* <DT>{@code height}</DT>
* <DD>integer, the height of the image
* <DT>{@code fontFamily}</DT>
* <DD>string, the name of the font family.
* Default is {@code "Helvetica"}.
* <DT>{@code fontSize}</DT>
* <DD>integer, the size of the font. Default is {@code 12}.
* <DT>{@code fontStyle}</DT>
* <DD>string, the tyle of the font. Can be one of the constants
* {@code plain} (default), {@code bold}, {@code italic} or
* {@code bolditalic}. Any other will result in {@code plain}.
* <DT>{@code fgcolor}</DT>
* <DD>color (HTML form, {@code #RRGGBB}), or color constant from
* {@link java.awt.Color}, default is {@code "black"}.
* <DT>{@code bgcolor}</DT>
* <DD>color (HTML form, {@code #RRGGBB}), or color constant from
* {@link java.awt.Color}, default is {@code "transparent"}.
* Note that the hash character ({@code "#"}) used in colors must be
* escaped as {@code %23} in the query string. See
* {@link StringUtil#toColor(String)}, <A href="#examples">examples</A>.
*
* <!-- inherited from ImageServlet below: -->
*
* <DT>{@code cache}</DT>
* <DD>boolean, {@code true} if you want to cache the result
* to disk (default).
*
* <DT>{@code compression}</DT>
* <DD>float, the optional compression ratio for the output image. For JPEG
* images, the quality is the inverse of the compression ratio. See
* {@link #JPEG_DEFAULT_COMPRESSION_LEVEL},
* {@link #PNG_DEFAULT_COMPRESSION_LEVEL}.
* <DD>Applies to JPEG and PNG images only.
*
* <DT>{@code dither}</DT>
* <DD>enumerated, one of {@code NONE}, {@code DEFAULT} or
* {@code FS}, if you want to dither the result ({@code DEFAULT} is
* default).
* {@code FS} will produce the best results, but it's slower.
* <DD>Use in conjuction with {@code indexed}, {@code palette}
* and {@code websafe}.
* <DD>Applies to GIF and PNG images only.
*
* <DT>{@code fileName}</DT>
* <DD>string, an optional filename. If not set, the path after the servlet
* ({@link HttpServletRequest#getPathInfo}) will be used for the cache
* filename. See {@link #getCacheFile(ServletRequest)},
* {@link #getCacheRoot}.
*
* <DT>{@code height}</DT>
* <DD>integer, the height of the image.
*
* <DT>{@code width}</DT>
* <DD>integer, the width of the image.
*
* <DT>{@code indexed}</DT>
* <DD>integer, the number of colors in the resulting image, or -1 (default).
* If the value is set and positive, the image will use an
* {@code IndexColorModel} with
* the number of colors specified. Otherwise the image will be true color.
* <DD>Applies to GIF and PNG images only.
*
* <DT>{@code palette}</DT>
* <DD>string, an optional filename. If set, the image will use IndexColorModel
* with a palette read from the given file.
* <DD>Applies to GIF and PNG images only.
*
* <DT>{@code websafe}</DT>
* <DD>boolean, {@code true} if you want the result to use the 216 color
* websafe palette (default is false).
* <DD>Applies to GIF and PNG images only.
* </DL>
*
* @example
* &lt;IMG src="/text/test.gif?height=40&width=600
* &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23990033
* &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
* %20lazy%20dog&cache=false" /&gt;
*
* @example
* &lt;IMG src="/text/test.jpg?height=40&width=600
* &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=black
* &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
* %20lazy%20dog&compression=3&cache=false" /&gt;
*
* @example
* &lt;IMG src="/text/test.png?height=40&width=600
* &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23336699
* &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
* %20lazy%20dog&cache=true" /&gt;
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java#2 $
*/
class TextRenderer /*extends ImageServlet implements ImagePainterServlet*/ {
// TODO: Create something useable out of this piece of old junk.. ;-)
// It just needs a graphics object to write onto
// Alternatively, defer, and compute the size needed
// Or, make it a filter...
/** {@code "italic"} */
public final static String FONT_STYLE_ITALIC = "italic";
/** {@code "plain"} */
public final static String FONT_STYLE_PLAIN = "plain";
/** {@code "bold"} */
public final static String FONT_STYLE_BOLD = "bold";
/** {@code text} */
public final static String PARAM_TEXT = "text";
/** {@code marginLeft} */
public final static String PARAM_MARGIN_LEFT = "marginLeft";
/** {@code marginTop} */
public final static String PARAM_MARGIN_TOP = "marginTop";
/** {@code fontFamily} */
public final static String PARAM_FONT_FAMILY = "fontFamily";
/** {@code fontSize} */
public final static String PARAM_FONT_SIZE = "fontSize";
/** {@code fontStyle} */
public final static String PARAM_FONT_STYLE = "fontStyle";
/** {@code textRotation} */
public final static String PARAM_TEXT_ROTATION = "textRotation";
/** {@code textRotation} */
public final static String PARAM_TEXT_ROTATION_UNITS = "textRotationUnits";
/** {@code bgcolor} */
public final static String PARAM_BGCOLOR = "bgcolor";
/** {@code fgcolor} */
public final static String PARAM_FGCOLOR = "fgcolor";
protected final static String ROTATION_DEGREES = "DEGREES";
protected final static String ROTATION_RADIANS = "RADIANS";
/**
* Creates the TextRender servlet.
*/
public TextRenderer() {
}
/**
* Renders the text string for this servlet request.
*/
private void paint(ServletRequest pReq, Graphics2D pRes,
int pWidth, int pHeight)
throws ImageServletException {
// Get parameters
String text = pReq.getParameter(PARAM_TEXT);
String[] lines = StringUtil.toStringArray(text, "\n\r");
String fontFamily = pReq.getParameter(PARAM_FONT_FAMILY);
String fontSize = pReq.getParameter(PARAM_FONT_SIZE);
String fontStyle = pReq.getParameter(PARAM_FONT_STYLE);
String bgcolor = pReq.getParameter(PARAM_BGCOLOR);
String fgcolor = pReq.getParameter(PARAM_FGCOLOR);
// TODO: Make them static..
pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
// pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
//System.out.println(pRes.getBackground());
// Clear area with bgcolor
if (!StringUtil.isEmpty(bgcolor)) {
pRes.setBackground(StringUtil.toColor(bgcolor));
pRes.clearRect(0, 0, pWidth, pHeight);
//System.out.println(pRes.getBackground());
}
// Create and set font
Font font = new Font((fontFamily != null ? fontFamily : "Helvetica"),
getFontStyle(fontStyle),
(fontSize != null ? Integer.parseInt(fontSize)
: 12));
pRes.setFont(font);
// Set rotation
double angle = getAngle(pReq);
pRes.rotate(angle, pWidth / 2.0, pHeight / 2.0);
// Draw string in fgcolor
pRes.setColor(fgcolor != null ? StringUtil.toColor(fgcolor)
: Color.black);
float x = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_LEFT,
Float.MIN_VALUE);
Rectangle2D[] bounds = new Rectangle2D[lines.length];
if (x <= Float.MIN_VALUE) {
// Center
float longest = 0f;
for (int i = 0; i < lines.length; i++) {
bounds[i] = font.getStringBounds(lines[i],
pRes.getFontRenderContext());
if (bounds[i].getWidth() > longest) {
longest = (float) bounds[i].getWidth();
}
}
//x = (float) ((pWidth - bounds.getWidth()) / 2f);
x = (float) ((pWidth - longest) / 2f);
//System.out.println("marginLeft: " + x);
}
//else {
//System.out.println("marginLeft (from param): " + x);
//}
float y = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_TOP,
Float.MIN_VALUE);
float lineHeight = (float) (bounds[0] != null ? bounds[0].getHeight() :
font.getStringBounds(lines[0],
pRes.getFontRenderContext()).getHeight());
if (y <= Float.MIN_VALUE) {
// Center
y = (float) ((pHeight - lineHeight) / 2f)
- (lineHeight * (lines.length - 2.5f) / 2f);
//System.out.println("marginTop: " + y);
}
else {
// Todo: Correct for font height?
y += font.getSize2D();
//System.out.println("marginTop (from param):" + y);
}
//System.out.println("Font size: " + font.getSize2D());
//System.out.println("Line height: " + lineHeight);
// Draw
for (int i = 0; i < lines.length; i++) {
pRes.drawString(lines[i], x, y + lineHeight * i);
}
}
/**
* Returns the font style constant.
*
* @param pStyle a string containing either the word {@code "plain"} or one
* or more of {@code "bold"} and {@code italic}.
* @return the font style constant as defined in {@link Font}.
*
* @see Font#PLAIN
* @see Font#BOLD
* @see Font#ITALIC
*/
private int getFontStyle(String pStyle) {
if (pStyle == null
|| StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_PLAIN)) {
return Font.PLAIN;
}
// Try to find bold/italic
int style = Font.PLAIN;
if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_BOLD)) {
style |= Font.BOLD;
}
if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_ITALIC)) {
style |= Font.ITALIC;
}
return style;
}
/**
* Gets the angle of rotation from the request.
*
* @param pRequest the servlet request to get parameters from
* @return the angle in radians.
*/
private double getAngle(ServletRequest pRequest) {
// Get angle
double angle =
ServletUtil.getDoubleParameter(pRequest, PARAM_TEXT_ROTATION, 0.0);
// Convert to radians, if needed
String units = pRequest.getParameter(PARAM_TEXT_ROTATION_UNITS);
if (!StringUtil.isEmpty(units)
&& ROTATION_DEGREES.equalsIgnoreCase(units)) {
angle = MathUtil.toRadians(angle);
}
return angle;
}
}

View File

@@ -0,0 +1,34 @@
/**
* Contains various image-outputting servlets, that should run under any servlet engine. To create your own image servlet, simply subclass the servlet
* {@code ImageServlet}. Optionally implement the interface
* {@code ImagePainterServlet}, if you want to do painting.
* <P>
* Some of these methods may require use of the native graphics libraries
* supported by the JVM, like the X libraries on Unix systems, and should be
* run with JRE <STRONG>1.4</STRONG> or later, and with the option:
* <DL>
* <DD>{@code -Djawa.awt.headless=true}</DD>
* </DL>
* See the document
* <A href="http://java.sun.com/j2se/1.4/docs/guide/awt/AWTChanges.html#headless">AWT Enhancements</A> and bugtraq report
* <A href="http://developer.java.sun.com/developer/bugParade/bugs/4281163.html">4281163</A> for more information on this issue.
* <P>
* If you cannot use JRE 1.4 for any reason, or do not want to use the X
* libraries, a possibilty is to use the
* <A href="http://www.eteks.com/pja/en/">PJA package</A> (com.eteks.pja),
* and start the JVM with the following options:
* <DL>
* <DD>{@code -Xbootclasspath/a:&lt;path to pja.jar&gt;}</DD>
* <DD>{@code -Dawt.toolkit=com.eteks.awt.PJAToolkit}</DD>
* <DD>{@code -Djava.awt.graphicsenv=com.eteks.java2d.PJAGraphicsEnvironment}</DD>
* <DD>{@code -Djava.awt.fonts=&lt;path where True Type fonts files will be loaded from&gt;}</DD>
* </DL>
* <P>
* Please note that creation of PNG images (from bytes or URL's) are only
* supported in JRE 1.3 and later, trying to load them from an earlier version,
* will result in errors.
*
* @see com.twelvemonkeys.servlet.image.ImageServlet
* @see com.twelvemonkeys.servlet.image.ImagePainterServlet
*/
package com.twelvemonkeys.servlet.image;

View File

@@ -0,0 +1,80 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: Droplet.java,v $
* Revision 1.3 2003/10/06 14:25:19 WMHAKUR
* Code clean-up only.
*
* Revision 1.2 2002/10/18 14:12:16 WMHAKUR
* Now, it even compiles. :-/
*
* Revision 1.1 2002/10/18 14:02:16 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import com.twelvemonkeys.servlet.jsp.droplet.taglib.*;
/**
* Dynamo Droplet like Servlet.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*
*/
public abstract class Droplet extends HttpServlet implements JspFragment {
// Copy doc
public abstract void service(PageContext pPageContext)
throws ServletException, IOException;
/**
* Services a parameter. Programatically equivalent to the
* <d:valueof param="pParameter"/> JSP tag.
*/
public void serviceParameter(String pParameter, PageContext pPageContext)
throws ServletException, IOException {
Object param = pPageContext.getRequest().getAttribute(pParameter);
if (param != null) {
if (param instanceof Param) {
((Param) param).service(pPageContext);
}
else {
pPageContext.getOut().print(param);
}
}
else {
// Try to get value from parameters
Object obj = pPageContext.getRequest().getParameter(pParameter);
// Print parameter or default value
pPageContext.getOut().print((obj != null) ? obj : "");
}
}
/**
* "There's no need to override this method." :-)
*/
final public void service(HttpServletRequest pRequest,
HttpServletResponse pResponse)
throws ServletException, IOException {
PageContext pageContext =
(PageContext) pRequest.getAttribute(IncludeTag.PAGE_CONTEXT);
// TODO: What if pageContext == null
service(pageContext);
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: JspFragment.java,v $
* Revision 1.2 2003/10/06 14:25:36 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/10/18 14:02:16 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
/**
* Interface for JSP sub pages or page fragments to implement.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*/
public interface JspFragment {
/**
* Services a sub page or a page fragment inside another page
* (or PageContext).
*
* @param pContext the PageContext that is used to render the subpage.
*
* @throws ServletException if an exception occurs that interferes with the
* subpage's normal operation
* @throws IOException if an input or output exception occurs
*/
public void service(PageContext pContext)
throws ServletException, IOException;
}

View File

@@ -0,0 +1,29 @@
package com.twelvemonkeys.servlet.jsp.droplet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
/**
* Oparam (Open parameter)
*/
public class Oparam extends Param implements JspFragment {
/**
* Creates an Oparam.
*
* @param pValue the value of the parameter
*/
public Oparam(String pValue) {
super(pValue);
}
public void service(PageContext pContext)
throws ServletException, IOException {
pContext.getServletContext().log("Service subpage " + pContext.getServletContext().getRealPath(mValue));
pContext.include(mValue);
}
}

View File

@@ -0,0 +1,42 @@
package com.twelvemonkeys.servlet.jsp.droplet;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
/**
* Param
*/
public class Param implements JspFragment {
/** The value member field. */
protected String mValue = null;
/**
* Creates a Param.
*
* @param pValue the value of the parameter
*/
public Param(String pValue) {
mValue = pValue;
}
/**
* Gets the value of the parameter.
*/
public String getValue() {
return mValue;
}
/**
* Services the page fragment. This version simply prints the value of
* this parameter to teh PageContext's out.
*/
public void service(PageContext pContext)
throws ServletException, IOException {
JspWriter writer = pContext.getOut();
writer.print(mValue);
}
}

View File

@@ -0,0 +1,10 @@
/**
* Dynamo Droplet-like functionality for JSP.
*
* This package is early beta, not for commercial use! :-)
* Read: The interfaces and classes in this package (and subpackages) will be
* developed and modified for a while.
*
* TODO: Insert taglib-descriptor here?
*/
package com.twelvemonkeys.servlet.jsp.droplet;

View File

@@ -0,0 +1,214 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: IncludeTag.java,v $
* Revision 1.2 2003/10/06 14:25:36 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/10/18 14:03:09 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* Include tag tag that emulates ATG Dynamo Droplet tag JHTML behaviour for
* JSP.
*
* @author Thomas Purcell (CSC Australia)
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*
*/
public class IncludeTag extends ExTagSupport {
/**
* This will contain the names of all the parameters that have been
* added to the PageContext.REQUEST_SCOPE scope by this tag.
*/
private ArrayList mParameterNames = null;
/**
* If any of the parameters we insert for this tag already exist, then
* we back up the older parameter in this {@code HashMap} and
* restore them when the tag is finished.
*/
private HashMap mOldParameters = null;
/**
* This is the URL for the JSP page that the parameters contained in this
* tag are to be inserted into.
*/
private String mPage;
/**
* The name of the PageContext attribute
*/
public final static String PAGE_CONTEXT = "com.twelvemonkeys.servlet.jsp.PageContext";
/**
* Sets the value for the JSP page to insert the parameters into. This
* will be set by the tag attribute within the original JSP page.
*
* @param pPage The URL for the JSP page to insert parameters into.
*/
public void setPage(String pPage) {
mPage = pPage;
}
/**
* Adds a parameter to the {@code PageContext.REQUEST_SCOPE} scope.
* If a parameter with the same name as {@code pName} already exists,
* then the old parameter is first placed in the {@code OldParameters}
* member variable. When this tag is finished, the old value will be
* restored.
*
* @param pName The name of the new parameter to be stored in the
* {@code PageContext.REQUEST_SCOPE} scope.
* @param pValue The value for the parmeter to be stored in the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
public void addParameter(String pName, Object pValue) {
// Check that we haven't already saved this parameter
if (!mParameterNames.contains(pName)) {
mParameterNames.add(pName);
// Now check if this parameter already exists in the page.
Object obj = getRequest().getAttribute(pName);
if (obj != null) {
mOldParameters.put(pName, obj);
}
}
// Finally, insert the parameter in the request scope.
getRequest().setAttribute(pName, pValue);
}
/**
* This is the method called when the JSP interpreter first hits the tag
* associated with this class. This method will firstly determine whether
* the page referenced by the {@code page} attribute exists. If the
* page doesn't exist, this method will throw a {@code JspException}.
* If the page does exist, this method will hand control over to that JSP
* page.
*
* @exception JspException
*/
public int doStartTag() throws JspException {
mOldParameters = new HashMap();
mParameterNames = new ArrayList();
return EVAL_BODY_INCLUDE;
}
/**
* This method is called when the JSP page compiler hits the end tag. By
* now all the data should have been passed and parameters entered into
* the {@code PageContext.REQUEST_SCOPE} scope. This method includes
* the JSP page whose URL is stored in the {@code mPage} member
* variable.
*
* @exception JspException
*/
public int doEndTag() throws JspException {
String msg;
try {
Iterator iterator;
String parameterName;
// -- Harald K 20020726
// Include the page, in place
//getDispatcher().include(getRequest(), getResponse());
addParameter(PAGE_CONTEXT, pageContext); // Will be cleared later
pageContext.include(mPage);
// Remove all the parameters that were added to the request scope
// for this insert tag.
iterator = mParameterNames.iterator();
while (iterator.hasNext()) {
parameterName = (String) iterator.next();
getRequest().removeAttribute(parameterName);
}
iterator = mOldParameters.keySet().iterator();
// Restore the parameters we temporarily replaced (if any).
while (iterator.hasNext()) {
parameterName = (String) iterator.next();
getRequest().setAttribute(parameterName, mOldParameters.get(parameterName));
}
return super.doEndTag();
}
catch (IOException ioe) {
msg = "Caught an IOException while including " + mPage
+ "\n" + ioe.toString();
log(msg, ioe);
throw new JspException(msg);
}
catch (ServletException se) {
msg = "Caught a ServletException while including " + mPage
+ "\n" + se.toString();
log(msg, se);
throw new JspException(msg);
}
}
/**
* Free up the member variables that we've used throughout this tag.
*/
protected void clearServiceState() {
mOldParameters = null;
mParameterNames = null;
}
/**
* Returns the request dispatcher for the JSP page whose URL is stored in
* the {@code mPage} member variable.
*
* @return The RequestDispatcher for the JSP page whose URL is stored in
* the {@code mPage} member variable.
*/
/*
private RequestDispatcher getDispatcher() {
return getRequest().getRequestDispatcher(mPage);
}
*/
/**
* Returns the HttpServletRequest object for the current user request.
*
* @return The HttpServletRequest object for the current user request.
*/
private HttpServletRequest getRequest() {
return (HttpServletRequest) pageContext.getRequest();
}
/**
* Returns the HttpServletResponse object for the current user request.
*
* @return The HttpServletResponse object for the current user request.
*/
private HttpServletResponse getResponse() {
return (HttpServletResponse) pageContext.getResponse();
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: NestingHandler.java,v $
* Revision 1.4 2003/10/06 14:25:44 WMHAKUR
* Code clean-up only.
*
* Revision 1.3 2003/08/04 15:26:30 WMHAKUR
* Code clean-up.
*
* Revision 1.2 2002/10/18 14:28:07 WMHAKUR
* Fixed package error.
*
* Revision 1.1 2002/10/18 14:03:09 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import com.twelvemonkeys.lang.StringUtil;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
/**
* A SAX handler that returns an exception if the nesting of
* {@code param}, {@code oparam}, {@code droplet} and
* {@code valueof} is not correct.
*
* Based on the NestingHandler.java,
* taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* &copy; 2002 Marty Hall; may be freely used or adapted.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*/
public class NestingHandler extends DefaultHandler {
private String mIncludeTagName = "include";
private String mParamTagName = "param";
private String mOpenParamTagName = "oparam";
//private Stack mParents = new Stack();
private boolean mInIncludeTag = false;
private String mNamespacePrefix = null;
private String mNamespaceURI = null;
private NestingValidator mValidator = null;
public NestingHandler(String pNamespacePrefix, String pNameSpaceURI,
NestingValidator pValidator) {
mNamespacePrefix = pNamespacePrefix;
mNamespaceURI = pNameSpaceURI;
mValidator = pValidator;
}
public void startElement(String pNamespaceURI, String pLocalName,
String pQualifiedName, Attributes pAttributes)
throws SAXException {
String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
? getNSPrefixFromURI(pNamespaceURI)
: getNamespacePrefix(pQualifiedName);
String localName = !StringUtil.isEmpty(pLocalName)
? pLocalName : getLocalName(pQualifiedName);
/*
if (namespacePrefix.equals(mNamespacePrefix)) {
System.out.println("startElement:\nnamespaceURI=" + pNamespaceURI
+ " namespacePrefix=" + namespacePrefix
+ " localName=" + localName
+ " qName=" + pQualifiedName
+ " attributes=" + pAttributes);
}
*/
if (localName.equals(mIncludeTagName)) {
// include
//System.out.println("<" + mNamespacePrefix + ":"
// + mIncludeTagName + ">");
if (mInIncludeTag) {
mValidator.reportError("Cannot nest " + namespacePrefix + ":"
+ mIncludeTagName);
}
mInIncludeTag = true;
}
else if (localName.equals(mParamTagName)) {
// param
//System.out.println("<" + mNamespacePrefix + ":"
// + mParamTagName + "/>");
if (!mInIncludeTag) {
mValidator.reportError(mNamespacePrefix + ":"
+ mParamTagName
+ " can only appear within "
+ mNamespacePrefix + ":"
+ mIncludeTagName);
}
}
else if (localName.equals(mOpenParamTagName)) {
// oparam
//System.out.println("<" + mNamespacePrefix + ":"
// + mOpenParamTagName + ">");
if (!mInIncludeTag) {
mValidator.reportError(mNamespacePrefix + ":"
+ mOpenParamTagName
+ " can only appear within "
+ mNamespacePrefix + ":"
+ mIncludeTagName);
}
mInIncludeTag = false;
}
else {
// Only jsp:text allowed inside include!
if (mInIncludeTag && !localName.equals("text")) {
mValidator.reportError(namespacePrefix + ":" + localName
+ " can not appear within "
+ mNamespacePrefix + ":"
+ mIncludeTagName);
}
}
}
public void endElement(String pNamespaceURI,
String pLocalName,
String pQualifiedName)
throws SAXException {
String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
? getNSPrefixFromURI(pNamespaceURI)
: getNamespacePrefix(pQualifiedName);
String localName = !StringUtil.isEmpty(pLocalName)
? pLocalName : getLocalName(pQualifiedName);
/*
if (namespacePrefix.equals(mNamespacePrefix)) {
System.out.println("endElement:\nnamespaceURI=" + pNamespaceURI
+ " namespacePrefix=" + namespacePrefix
+ " localName=" + localName
+ " qName=" + pQualifiedName);
}
*/
if (namespacePrefix.equals(mNamespacePrefix)
&& localName.equals(mIncludeTagName)) {
//System.out.println("</" + mNamespacePrefix + ":"
// + mIncludeTagName + ">");
mInIncludeTag = false;
}
else if (namespacePrefix.equals(mNamespacePrefix)
&& localName.equals(mOpenParamTagName)) {
//System.out.println("</" + mNamespacePrefix + ":"
// + mOpenParamTagName + ">");
mInIncludeTag = true; // assuming no errors before this...
}
}
/**
* Stupid broken namespace-support "fix"..
*/
private String getNSPrefixFromURI(String pNamespaceURI) {
return (pNamespaceURI.equals(mNamespaceURI)
? mNamespacePrefix : "");
}
private String getNamespacePrefix(String pQualifiedName) {
return pQualifiedName.substring(0, pQualifiedName.indexOf(':'));
}
private String getLocalName(String pQualifiedName) {
return pQualifiedName.substring(pQualifiedName.indexOf(':') + 1);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: NestingValidator.java,v $
* Revision 1.4 2003/08/04 15:26:40 WMHAKUR
* Code clean-up.
*
* Revision 1.3 2002/11/18 14:12:43 WMHAKUR
* *** empty log message ***
*
* Revision 1.2 2002/10/18 14:28:07 WMHAKUR
* Fixed package error.
*
* Revision 1.1 2002/10/18 14:03:09 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import java.util.*;
import javax.servlet.jsp.tagext.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import com.twelvemonkeys.util.*;
/**
* A validator that verifies that tags follow
* proper nesting order.
* <P>
* Based on NestingValidator.java,
* taken from More Servlets and JavaServer Pages
* from Prentice Hall and Sun Microsystems Press,
* http://www.moreservlets.com/.
* &copy; 2002 Marty Hall; may be freely used or adapted.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*
*/
public class NestingValidator extends TagLibraryValidator {
private Vector errors = new Vector();
/**
*
*/
public ValidationMessage[] validate(String pPrefix,
String pURI,
PageData pPage) {
//System.out.println("Validating " + pPrefix + " (" + pURI + ") for "
// + pPage + ".");
// Pass the parser factory in on the command line with
// -D to override the use of the Apache parser.
DefaultHandler handler = new NestingHandler(pPrefix, pURI, this);
SAXParserFactory factory = SAXParserFactory.newInstance();
try {
// FileUtil.copy(pPage.getInputStream(), System.out);
SAXParser parser = factory.newSAXParser();
InputSource source =
new InputSource(pPage.getInputStream());
// Parse, handler will use callback to report errors
parser.parse(source, handler);
}
catch (Exception e) {
String errorMessage = e.getMessage();
reportError(errorMessage);
}
// Return any errors and exceptions, empty array means okay
return (ValidationMessage[])
errors.toArray(new ValidationMessage[errors.size()]);
}
/**
* Callback method for the handler to report errors
*/
public void reportError(String pMessage) {
// The first argument to the ValidationMessage
// constructor can be a tag ID. Since tag IDs
// are not universally supported, use null for
// portability. The important part is the second
// argument: the error message.
errors.add(new ValidationMessage(null, pMessage));
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: OparamTag.java,v $
* Revision 1.4 2003/10/06 14:25:53 WMHAKUR
* Code clean-up only.
*
* Revision 1.3 2002/11/18 14:12:43 WMHAKUR
* *** empty log message ***
*
* Revision 1.2 2002/11/07 12:20:14 WMHAKUR
* Updated to reflect changes in com.twelvemonkeys.util.*Util
*
* Revision 1.1 2002/10/18 14:03:09 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import com.twelvemonkeys.io.FileUtil;
import com.twelvemonkeys.lang.StringUtil;
import com.twelvemonkeys.servlet.jsp.droplet.Oparam;
import com.twelvemonkeys.servlet.jsp.taglib.BodyReaderTag;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTag;
import javax.servlet.jsp.tagext.Tag;
import java.io.File;
import java.io.IOException;
/**
* Open parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
*
* @author Thomas Purcell (CSC Australia)
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java#1 $
*/
public class OparamTag extends BodyReaderTag {
protected final static String COUNTER = "com.twelvemonkeys.servlet.jsp.taglib.OparamTag.counter";
private File mSubpage = null;
/**
* This is the name of the parameter to be inserted into the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
private String mParameterName = null;
private String mLanguage = null;
private String mPrefix = null;
/**
* This method allows the JSP page to set the name for the parameter by
* using the {@code name} tag attribute.
*
* @param pName The name for the parameter to insert into the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
public void setName(String pName) {
mParameterName = pName;
}
public void setLanguage(String pLanguage) {
//System.out.println("setLanguage:"+pLanguage);
mLanguage = pLanguage;
}
public void setPrefix(String pPrefix) {
//System.out.println("setPrefix:"+pPrefix);
mPrefix = pPrefix;
}
/**
* Ensure that the tag implemented by this class is enclosed by an {@code
* IncludeTag}. If the tag is not enclosed by an
* {@code IncludeTag} then a {@code JspException} is thrown.
*
* @return If this tag is enclosed within an {@code IncludeTag}, then
* the default return value from this method is the {@code
* TagSupport.EVAL_BODY_TAG} value.
* @exception JspException
*/
public int doStartTag() throws JspException {
//checkEnclosedInIncludeTag(); // Moved to TagLibValidator
// Get request
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
// Get filename
mSubpage = createFileNameFromRequest(request);
// Get include tag, and add to parameters
IncludeTag includeTag = (IncludeTag) getParent();
includeTag.addParameter(mParameterName, new Oparam(mSubpage.getName()));
// if ! subpage.exist || jsp newer than subpage, write new
File jsp = new File(pageContext.getServletContext()
.getRealPath(request.getServletPath()));
if (!mSubpage.exists() || jsp.lastModified() > mSubpage.lastModified()) {
return BodyTag.EVAL_BODY_BUFFERED;
}
// No need to evaluate body again!
return Tag.SKIP_BODY;
}
/**
* This is the method responsible for actually testing that the tag
* implemented by this class is enclosed within an {@code IncludeTag}.
*
* @exception JspException
*/
/*
protected void checkEnclosedInIncludeTag() throws JspException {
Tag parentTag = getParent();
if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
return;
}
String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
"is not enclosed within an IncludeTag.";
log(msg);
throw new JspException(msg);
}
*/
/**
* This method cleans up the member variables for this tag in preparation
* for being used again. This method is called when the tag finishes it's
* current call with in the page but could be called upon again within this
* same page. This method is also called in the release stage of the tag
* life cycle just in case a JspException was thrown during the tag
* execution.
*/
protected void clearServiceState() {
mParameterName = null;
}
/**
* This is the method responsible for taking the result of the JSP code
* that forms the body of this tag and inserts it as a parameter into the
* request scope session. If any problems occur while loading the body
* into the session scope then a {@code JspException} will be thrown.
*
* @param pContent The body of the tag as a String.
*
* @exception JspException
*/
protected void processBody(String pContent) throws JspException {
// Okay, we have the content, we need to write it to disk somewhere
String content = pContent;
if (!StringUtil.isEmpty(mLanguage)) {
content = "<%@page language=\"" + mLanguage + "\" %>" + content;
}
if (!StringUtil.isEmpty(mPrefix)) {
content = "<%@taglib uri=\"/twelvemonkeys-common\" prefix=\"" + mPrefix + "\" %>" + content;
}
// Write the content of the oparam to disk
try {
log("Processing subpage " + mSubpage.getPath());
FileUtil.write(mSubpage, content.getBytes());
}
catch (IOException ioe) {
throw new JspException(ioe);
}
}
/**
* Creates a unique filename for each (nested) oparam
*/
private File createFileNameFromRequest(HttpServletRequest pRequest) {
//System.out.println("ServletPath" + pRequest.getServletPath());
String path = pRequest.getServletPath();
// Find last '/'
int splitIndex = path.lastIndexOf("/");
// Split -> path + name
String name = path.substring(splitIndex + 1);
path = path.substring(0, splitIndex);
// Replace special chars in name with '_'
name = name.replace('.', '_');
String param = mParameterName.replace('.', '_');
param = param.replace('/', '_');
param = param.replace('\\', '_');
param = param.replace(':', '_');
// tempfile = realPath(path) + name + "_oparam_" + number + ".jsp"
int count = getOparamCountFromRequest(pRequest);
// Hmm.. Would be great, but seems like I can't serve pages from within the temp dir
//File temp = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
//return new File(new File(temp, path), name + "_oparam_" + count + "_" + param + ".jsp");
return new File(new File(pageContext.getServletContext().getRealPath(path)), name + "_oparam_" + count + "_" + param + ".jsp");
}
/**
* Gets the current oparam count for this request
*/
private int getOparamCountFromRequest(HttpServletRequest pRequest) {
// Use request.attribute for incrementing oparam counter
Integer count = (Integer) pRequest.getAttribute(COUNTER);
if (count == null)
count = new Integer(0);
else
count = new Integer(count.intValue() + 1);
// ... and set it back
pRequest.setAttribute(COUNTER, count);
return count.intValue();
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ParamTag.java,v $
* Revision 1.2 2003/10/06 14:26:00 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/10/18 14:03:09 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.twelvemonkeys.servlet.jsp.droplet.*;
import com.twelvemonkeys.servlet.jsp.taglib.*;
/**
* Parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
*
* @author Thomas Purcell (CSC Australia)
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*
*/
public class ParamTag extends ExTagSupport {
/**
* This is the name of the parameter to be inserted into the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
private String mParameterName;
/**
* This is the value for the parameter to be inserted into the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
private Object mParameterValue;
/**
* This method allows the JSP page to set the name for the parameter by
* using the {@code name} tag attribute.
*
* @param pName The name for the parameter to insert into the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
public void setName(String pName) {
mParameterName = pName;
}
/**
* This method allows the JSP page to set the value for hte parameter by
* using the {@code value} tag attribute.
*
* @param pValue The value for the parameter to insert into the <code>
* PageContext.REQUEST_SCOPE</page> scope.
*/
public void setValue(String pValue) {
mParameterValue = new Param(pValue);
}
/**
* Ensure that the tag implemented by this class is enclosed by an {@code
* IncludeTag}. If the tag is not enclosed by an
* {@code IncludeTag} then a {@code JspException} is thrown.
*
* @return If this tag is enclosed within an {@code IncludeTag}, then
* the default return value from this method is the {@code
* TagSupport.SKIP_BODY} value.
* @exception JspException
*/
public int doStartTag() throws JspException {
//checkEnclosedInIncludeTag();
addParameter();
return SKIP_BODY;
}
/**
* This is the method responsible for actually testing that the tag
* implemented by this class is enclosed within an {@code IncludeTag}.
*
* @exception JspException
*/
/*
protected void checkEnclosedInIncludeTag() throws JspException {
Tag parentTag = getParent();
if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
return;
}
String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
"is not enclosed within an IncludeTag.";
log(msg);
throw new JspException(msg);
}
*/
/**
* This method adds the parameter whose name and value were passed to this
* object via the tag attributes to the parent {@code Include} tag.
*/
private void addParameter() {
IncludeTag includeTag = (IncludeTag) getParent();
includeTag.addParameter(mParameterName, mParameterValue);
}
/**
* This method cleans up the member variables for this tag in preparation
* for being used again. This method is called when the tag finishes it's
* current call with in the page but could be called upon again within this
* same page. This method is also called in the release stage of the tag
* life cycle just in case a JspException was thrown during the tag
* execution.
*/
protected void clearServiceState() {
mParameterName = null;
mParameterValue = null;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ValueOfTEI.java,v $
* Revision 1.3 2003/10/06 14:26:07 WMHAKUR
* Code clean-up only.
*
* Revision 1.2 2002/10/18 14:28:07 WMHAKUR
* Fixed package error.
*
* Revision 1.1 2002/10/18 14:03:52 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import java.io.IOException;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* TagExtraInfo for ValueOf.
* @todo More meaningful response to the user.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*
*/
public class ValueOfTEI extends TagExtraInfo {
public boolean isValid(TagData pTagData) {
Object nameAttr = pTagData.getAttribute("name");
Object paramAttr = pTagData.getAttribute("param");
if ((nameAttr != null && paramAttr == null) ||
(nameAttr == null && paramAttr != null)) {
return true; // Exactly one of name or param set
}
// Either both or none,
return false;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ValueOfTag.java,v $
* Revision 1.2 2003/10/06 14:26:14 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/10/18 14:03:52 WMHAKUR
* Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
*
*
*/
package com.twelvemonkeys.servlet.jsp.droplet.taglib;
import java.io.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import com.twelvemonkeys.servlet.jsp.droplet.*;
import com.twelvemonkeys.servlet.jsp.taglib.*;
/**
* ValueOf tag that emulates ATG Dynamo JHTML behaviour for JSP.
*
* @author Thomas Purcell (CSC Australia)
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
*
* @version $Revision: #1 $, ($Date: 2008/05/05 $)
*/
public class ValueOfTag extends ExTagSupport {
/**
* This is the name of the parameter whose value is to be inserted into
* the current JSP page. This value will be set via the {@code name}
* attribute.
*/
private String mParameterName;
/**
* This is the value of the parameter read from the {@code
* PageContext.REQUEST_SCOPE} scope. If the parameter doesn't exist,
* then this will be null.
*/
private Object mParameterValue;
/**
* This method is called as part of the initialisation phase of the tag
* life cycle. It sets the parameter name to be read from the {@code
* PageContext.REQUEST_SCOPE} scope.
*
* @param pName The name of the parameter to be read from the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
public void setName(String pName) {
mParameterName = pName;
}
/**
* This method is called as part of the initialisation phase of the tag
* life cycle. It sets the parameter name to be read from the {@code
* PageContext.REQUEST_SCOPE} scope. This is just a synonym for
* setName, to be more like ATG Dynamo.
*
* @param pName The name of the parameter to be read from the {@code
* PageContext.REQUEST_SCOPE} scope.
*/
public void setParam(String pName) {
mParameterName = pName;
}
/**
* This method looks in the session scope for the session-scoped attribute
* whose name matches the {@code name} tag attribute for this tag.
* If it finds it, then it replaces this tag with the value for the
* session-scoped attribute. If it fails to find the session-scoped
* attribute, it displays the body for this tag.
*
* @return If the session-scoped attribute is found, then this method will
* return {@code TagSupport.SKIP_BODY}, otherwise it will return
* {@code TagSupport.EVAL_BODY_INCLUDE}.
* @exception JspException
*
*/
public int doStartTag() throws JspException {
try {
if (parameterExists()) {
if (mParameterValue instanceof JspFragment) {
// OPARAM or PARAM
((JspFragment) mParameterValue).service(pageContext);
/*
log("Service subpage " + pageContext.getServletContext().getRealPath(((Oparam) mParameterValue).getName()));
pageContext.include(((Oparam) mParameterValue).getName());
*/
}
else {
// Normal JSP parameter value
JspWriter writer = pageContext.getOut();
writer.print(mParameterValue);
}
return SKIP_BODY;
}
else {
return EVAL_BODY_INCLUDE;
}
}
catch (ServletException se) {
log(se.getMessage(), se);
throw new JspException(se);
}
catch (IOException ioe) {
String msg = "Caught an IOException in ValueOfTag.doStartTag()\n"
+ ioe.toString();
log(msg, ioe);
throw new JspException(msg);
}
}
/**
* This method is used to determine whether the parameter whose name is
* stored in {@code mParameterName} exists within the {@code
* PageContext.REQUEST_SCOPE} scope. If the parameter does exist,
* then this method will return {@code true}, otherwise it returns
* {@code false}. This method has the side affect of loading the
* parameter value into {@code mParameterValue} if the parameter
* does exist.
*
* @return {@code true} if the parameter whose name is in {@code
* mParameterName} exists in the {@code PageContext.REQUEST_SCOPE
* } scope, {@code false} otherwise.
*/
private boolean parameterExists() {
mParameterValue = pageContext.getAttribute(mParameterName, PageContext.REQUEST_SCOPE);
// -- Harald K 20020726
if (mParameterValue == null) {
mParameterValue = pageContext.getRequest().getParameter(mParameterName);
}
return (mParameterValue != null);
}
}

View File

@@ -0,0 +1,10 @@
<HTML>
<BODY>
The TwelveMonkeys droplet TagLib.
TODO: Insert taglib-descriptor here?
</BODY>
</HTML>

View File

@@ -0,0 +1,4 @@
/**
* JSP support.
*/
package com.twelvemonkeys.servlet.jsp;

View File

@@ -0,0 +1,43 @@
package com.twelvemonkeys.servlet.jsp.taglib;
import javax.servlet.jsp.JspException;
/**
*
*
* @author Thomas Purcell (CSC Australia)
*
* @version 1.0
*/
public abstract class BodyReaderTag extends ExBodyTagSupport {
/**
* This is the method called by the JSP engine when the body for a tag
* has been parsed and is ready for inclusion in this current tag. This
* method takes the content as a string and passes it to the {@code
* processBody} method.
*
* @return This method returns the {@code BodyTagSupport.SKIP_BODY}
* constant. This means that the body of the tag will only be
* processed the one time.
* @exception JspException
*/
public int doAfterBody() throws JspException {
processBody(bodyContent.getString());
return SKIP_BODY;
}
/**
* This is the method that child classes must implement. It takes the
* body of the tag converted to a String as it's parameter. The body of
* the tag will have been interpreted to a String by the JSP engine before
* this method is called.
*
* @param pContent The body for the custom tag converted to a String.
* @exception JscException
*/
protected abstract void processBody(String pContent) throws JspException;
}

View File

@@ -0,0 +1,240 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: CSVToTableTag.java,v $
* Revision 1.3 2003/10/06 14:24:50 WMHAKUR
* Code clean-up only.
*
* Revision 1.2 2002/11/26 17:33:49 WMHAKUR
* Added documentation & removed System.out.println()s.
*
* Revision 1.1 2002/11/19 10:50:10 WMHAKUR
* Renamed from CSVToTable, to follow naming conventions.
*
* Revision 1.1 2002/11/18 22:11:16 WMHAKUR
* Tag to convert CSV to HTML table.
* Can be further transformed, using XSLT.
*
*/
package com.twelvemonkeys.servlet.jsp.taglib;
import java.util.*;
import java.io.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* Creates a table from a string of "comma-separated values" (CSV).
* The delimiter character can be any character (or combination of characters).
* The default delimiter is TAB ({@code \t}).
*
* <P/>
* <HR/>
* <P/>
*
* The input may look like this:
* <PRE>
* &lt;c:totable firstRowIsHeader="true" delimiter=";"&gt;
* header A;header B
* data 1A; data 1B
* data 2A; data 2B
* &lt;/c:totable&gt;
* </PRE>
*
* The output (source) will look like this:
* <PRE>
* &lt;TABLE&gt;
* &lt;TR&gt;
* &lt;TH&gt;header A&lt;/TH&gt;&lt;TH&gt;header B&lt;/TH&gt;
* &lt;/TR&gt;
* &lt;TR&gt;
* &lt;TD&gt;data 1A&lt;/TD&gt;&lt;TD&gt;data 1B&lt;/TD&gt;
* &lt;/TR&gt;
* &lt;TR&gt;
* &lt;TD&gt;data 2A&lt;/TD&gt;&lt;TD&gt;data 2B&lt;/TD&gt;
* &lt;/TR&gt;
* &lt;/TABLE&gt;
* </PRE>
* You wil probably want to use XSLT to make the final output look nicer. :-)
*
* @see StringTokenizer
* @see <A href="http://www.w3.org/TR/xslt">XSLT spec</A>
*
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java#1 $
*/
public class CSVToTableTag extends ExBodyTagSupport {
public final static String TAB = "\t";
protected String mDelimiter = null;
protected boolean mFirstRowIsHeader = false;
protected boolean mFirstColIsHeader = false;
public void setDelimiter(String pDelimiter) {
mDelimiter = pDelimiter;
}
public String getDelimiter() {
return mDelimiter != null ? mDelimiter : TAB;
}
public void setFirstRowIsHeader(String pBoolean) {
mFirstRowIsHeader = Boolean.valueOf(pBoolean).booleanValue();
}
public void setFirstColIsHeader(String pBoolean) {
mFirstColIsHeader = Boolean.valueOf(pBoolean).booleanValue();
}
public int doEndTag() throws JspException {
BodyContent content = getBodyContent();
try {
Table table =
Table.parseContent(content.getReader(), getDelimiter());
JspWriter out = pageContext.getOut();
//System.out.println("CSVToTable: " + table.getRows() + " rows, "
// + table.getCols() + " cols.");
if (table.getRows() > 0) {
out.println("<TABLE>");
// Loop over rows
for (int row = 0; row < table.getRows(); row++) {
out.println("<TR>");
// Loop over cells in each row
for (int col = 0; col < table.getCols(); col++) {
// Test if we are using headers, else normal cell
if (mFirstRowIsHeader && row == 0
|| mFirstColIsHeader && col == 0) {
out.println("<TH>" + table.get(row, col)
+ " </TH>");
}
else {
out.println("<TD>" + table.get(row, col)
+ " </TD>");
}
}
out.println("</TR>");
}
out.println("</TABLE>");
}
}
catch (IOException ioe) {
throw new JspException(ioe);
}
return super.doEndTag();
}
static class Table {
List mRows = null;
int mCols = 0;
private Table(List pRows, int pCols) {
mRows = pRows;
mCols = pCols;
}
int getRows() {
return mRows != null ? mRows.size() : 0;
}
int getCols() {
return mCols;
}
List getTableRows() {
return mRows;
}
List getTableRow(int pRow) {
return mRows != null
? (List) mRows.get(pRow)
: Collections.EMPTY_LIST;
}
String get(int pRow, int pCol) {
List row = getTableRow(pRow);
// Rows may contain unequal number of cols
return (row.size() > pCol) ? (String) row.get(pCol) : "";
}
/**
* Parses a BodyContent to a table.
*
*/
static Table parseContent(Reader pContent, String pDelim)
throws IOException {
ArrayList tableRows = new ArrayList();
int tdsPerTR = 0;
// Loop through TRs
BufferedReader reader = new BufferedReader(pContent);
String tr = null;
while ((tr = reader.readLine()) != null) {
// Discard blank lines
if (tr != null
&& tr.trim().length() <= 0 && tr.indexOf(pDelim) < 0) {
continue;
}
//System.out.println("CSVToTable: read LINE=\"" + tr + "\"");
ArrayList tableDatas = new ArrayList();
StringTokenizer tableRow = new StringTokenizer(tr, pDelim,
true);
boolean lastWasDelim = false;
while (tableRow.hasMoreTokens()) {
String td = tableRow.nextToken();
//System.out.println("CSVToTable: read data=\"" + td + "\"");
// Test if we have empty TD
if (td.equals(pDelim)) {
if (lastWasDelim) {
// Add empty TD
tableDatas.add("");
}
// We just read a delimitter
lastWasDelim = true;
}
else {
// No tab, normal data
lastWasDelim = false;
// Add normal TD
tableDatas.add(td);
}
} // end while (tableRow.hasNext())
// Store max TD count
if (tableDatas.size() > tdsPerTR) {
tdsPerTR = tableDatas.size();
}
// Add a table row
tableRows.add(tableDatas);
}
// Return TABLE
return new Table(tableRows, tdsPerTR);
}
}
}

View File

@@ -0,0 +1,286 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ExBodyTagSupport.java,v $
* Revision 1.3 2003/10/06 14:24:57 WMHAKUR
* Code clean-up only.
*
* Revision 1.2 2002/11/18 22:10:27 WMHAKUR
* *** empty log message ***
*
*
*/
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* This is the class that should be extended by all jsp pages that do use their
* body. It contains a lot of helper methods for simplifying common tasks.
*
* @author Thomas Purcell (CSC Australia)
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java#1 $
*/
public class ExBodyTagSupport extends BodyTagSupport implements ExTag {
/**
* writeHtml ensures that the text being outputted appears as it was
* entered. This prevents users from hacking the system by entering
* html or jsp code into an entry form where that value will be displayed
* later in the site.
*
* @param pOut The JspWriter to write the output to.
* @param pHtml The original html to filter and output to the user.
* @throws IOException If the user clicks Stop in the browser, or their
* browser crashes, then the JspWriter will throw an IOException when
* the jsp tries to write to it.
*/
public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
while (parser.hasMoreTokens()) {
String token = parser.nextToken();
if (token.equals("<")) {
pOut.print("&lt;");
}
else if (token.equals(">")) {
pOut.print("&gt;");
}
else if (token.equals("&")) {
pOut.print("&amp;");
}
else {
pOut.print(token);
}
}
}
/**
* Log a message to the servlet context.
*
* @param pMsg The error message to log.
*/
public void log(String pMsg) {
getServletContext().log(pMsg);
}
/**
* Log a message to the servlet context and include the exception that is
* passed in as the second parameter.
*
* @param pMsg The error message to log.
* @param pException The exception that caused this error message to be
* logged.
*/
public void log(String pMsg, Throwable pException) {
getServletContext().log(pMsg, pException);
}
/**
* Retrieves the ServletContext object associated with the current
* PageContext object.
*
* @return The ServletContext object associated with the current
* PageContext object.
*/
public ServletContext getServletContext() {
return pageContext.getServletContext();
}
/**
* Called when the tag has finished running. Any clean up that needs
* to be done between calls to this tag but within the same JSP page is
* called in the {@code clearServiceState()} method call.
*
* @exception JspException
*/
public int doEndTag() throws JspException {
clearServiceState();
return super.doEndTag();
}
/**
* Called when a tag's role in the current JSP page is finished. After
* the {@code clearProperties()} method is called, the custom tag
* should be in an identical state as when it was first created. The
* {@code clearServiceState()} method is called here just in case an
* exception was thrown in the custom tag. If an exception was thrown,
* then the {@code doEndTag()} method will not have been called and
* the tag might not have been cleaned up properly.
*/
public void release() {
clearServiceState();
clearProperties();
super.release();
}
/**
* The default implementation for the {@code clearProperties()}. Not
* all tags will need to overload this method call. By implementing it
* here, all classes that extend this object are able to call {@code
* super.clearProperties()}. So, if the class extends a different
* tag, or this one, the parent method should always be called. This
* method will be called when the tag is to be released. That is, the
* tag has finished for the current page and should be returned to it's
* initial state.
*/
protected void clearProperties() {
}
/**
* The default implementation for the {@code clearServiceState()}.
* Not all tags will need to overload this method call. By implementing it
* here, all classes that extend this object are able to call {@code
* super.clearServiceState()}. So, if the class extends a different
* tag, or this one, the parent method should always be called. This
* method will be called when the tag has finished it's current tag
* within the page, but may be called upon again in this same JSP page.
*/
protected void clearServiceState() {
}
/**
* Returns the initialisation parameter from the {@code
* PageContext.APPLICATION_SCOPE} scope. These initialisation
* parameters are defined in the {@code web.xml} configuration file.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @return The value for the parameter whose name was passed in as a
* parameter. If the parameter does not exist, then {@code null}
* will be returned.
*/
public String getInitParameter(String pName) {
return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
}
/**
* Returns an Enumeration containing all the names for all the
* initialisation parametes defined in the {@code
* PageContext.APPLICATION_SCOPE} scope.
*
* @return An {@code Enumeration} containing all the names for all the
* initialisation parameters.
*/
public Enumeration getInitParameterNames() {
return getInitParameterNames(PageContext.APPLICATION_SCOPE);
}
/**
* Returns the initialisation parameter from the scope specified with the
* name specified.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @param pScope The scope to search for the initialisation parameter
* within.
* @return The value of the parameter found. If no parameter with the
* name specified is found in the scope specified, then {@code null
* } is returned.
*/
public String getInitParameter(String pName, int pScope) {
switch (pScope) {
case PageContext.PAGE_SCOPE:
return getServletConfig().getInitParameter(pName);
case PageContext.APPLICATION_SCOPE:
return getServletContext().getInitParameter(pName);
default:
throw new IllegalArgumentException("Illegal scope.");
}
}
/**
* Returns an enumeration containing all the parameters defined in the
* scope specified by the parameter.
*
* @param pScope The scope to return the names of all the parameters
* defined within.
* @return An {@code Enumeration} containing all the names for all the
* parameters defined in the scope passed in as a parameter.
*/
public Enumeration getInitParameterNames(int pScope) {
switch (pScope) {
case PageContext.PAGE_SCOPE:
return getServletConfig().getInitParameterNames();
case PageContext.APPLICATION_SCOPE:
return getServletContext().getInitParameterNames();
default:
throw new IllegalArgumentException("Illegal scope");
}
}
/**
* Returns the servlet config associated with the current JSP page request.
*
* @return The {@code ServletConfig} associated with the current
* request.
*/
public ServletConfig getServletConfig() {
return pageContext.getServletConfig();
}
/**
* Gets the context path associated with the current JSP page request.
* If the request is not a HttpServletRequest, this method will
* return "/".
*
* @return a path relative to the current context's root, or
* {@code "/"} if this is not a HTTP request.
*/
public String getContextPath() {
ServletRequest request = pageContext.getRequest();
if (request instanceof HttpServletRequest) {
return ((HttpServletRequest) request).getContextPath();
}
return "/";
}
/**
* Gets the resource associated with the given relative path for the
* current JSP page request.
* The path may be absolute, or relative to the current context root.
*
* @param pPath the path
*
* @return a path relative to the current context root
*/
public InputStream getResourceAsStream(String pPath) {
// throws MalformedURLException {
String path = pPath;
if (pPath != null && !pPath.startsWith("/")) {
path = getContextPath() + pPath;
}
return pageContext.getServletContext().getResourceAsStream(path);
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ExTag.java,v $
* Revision 1.2 2003/10/06 14:25:05 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/11/18 22:10:27 WMHAKUR
* *** empty log message ***
*
*
*/
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* This interface contains a lot of helper methods for simplifying common
* taglib related tasks.
*
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java#1 $
*/
public interface ExTag extends Tag {
/**
* writeHtml ensures that the text being outputted appears as it was
* entered. This prevents users from hacking the system by entering
* html or jsp code into an entry form where that value will be displayed
* later in the site.
*
* @param pOut The JspWriter to write the output to.
* @param pHtml The original html to filter and output to the user.
* @throws IOException If the user clicks Stop in the browser, or their
* browser crashes, then the JspWriter will throw an IOException when
* the jsp tries to write to it.
*/
public void writeHtml(JspWriter pOut, String pHtml) throws IOException;
/**
* Log a message to the servlet context.
*
* @param pMsg The error message to log.
*/
public void log(String pMsg);
/**
* Logs a message to the servlet context and include the exception that is
* passed in as the second parameter.
*
* @param pMsg The error message to log.
* @param pException The exception that caused this error message to be
* logged.
*/
public void log(String pMsg, Throwable pException);
/**
* Retrieves the ServletContext object associated with the current
* PageContext object.
*
* @return The ServletContext object associated with the current
* PageContext object.
*/
public ServletContext getServletContext();
/**
* Returns the initialisation parameter from the {@code
* PageContext.APPLICATION_SCOPE} scope. These initialisation
* parameters are defined in the {@code web.xml} configuration file.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @return The value for the parameter whose name was passed in as a
* parameter. If the parameter does not exist, then {@code null}
* will be returned.
*/
public String getInitParameter(String pName);
/**
* Returns an Enumeration containing all the names for all the
* initialisation parametes defined in the {@code
* PageContext.APPLICATION_SCOPE} scope.
*
* @return An {@code Enumeration} containing all the names for all the
* initialisation parameters.
*/
public Enumeration getInitParameterNames();
/**
* Returns the initialisation parameter from the scope specified with the
* name specified.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @param pScope The scope to search for the initialisation parameter
* within.
* @return The value of the parameter found. If no parameter with the
* name specified is found in the scope specified, then {@code null
* } is returned.
*/
public String getInitParameter(String pName, int pScope);
/**
* Returns an enumeration containing all the parameters defined in the
* scope specified by the parameter.
*
* @param pScope The scope to return the names of all the parameters
* defined within.
* @return An {@code Enumeration} containing all the names for all the
* parameters defined in the scope passed in as a parameter.
*/
public Enumeration getInitParameterNames(int pScope);
/**
* Returns the servlet config associated with the current JSP page request.
*
* @return The {@code ServletConfig} associated with the current
* request.
*/
public ServletConfig getServletConfig();
/**
* Gets the context path associated with the current JSP page request.
*
* @return a path relative to the current context's root.
*/
public String getContextPath();
/**
* Gets the resource associated with the given relative path for the
* current JSP page request.
* The path may be absolute, or relative to the current context root.
*
* @param pPath the path
*
* @return a path relative to the current context root
*/
public InputStream getResourceAsStream(String pPath);
}

View File

@@ -0,0 +1,289 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: ExTagSupport.java,v $
* Revision 1.3 2003/10/06 14:25:11 WMHAKUR
* Code clean-up only.
*
* Revision 1.2 2002/11/18 22:10:27 WMHAKUR
* *** empty log message ***
*
*
*/
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* This is the class that should be extended by all jsp pages that don't use
* their body. It contains a lot of helper methods for simplifying common
* tasks.
*
* @author Thomas Purcell (CSC Australia)
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java#1 $
*/
public class ExTagSupport extends TagSupport implements ExTag {
/**
* writeHtml ensures that the text being outputted appears as it was
* entered. This prevents users from hacking the system by entering
* html or jsp code into an entry form where that value will be displayed
* later in the site.
*
* @param pOut The JspWriter to write the output to.
* @param pHtml The original html to filter and output to the user.
* @throws IOException If the user clicks Stop in the browser, or their
* browser crashes, then the JspWriter will throw an IOException when
* the jsp tries to write to it.
*/
public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
while (parser.hasMoreTokens()) {
String token = parser.nextToken();
if (token.equals("<")) {
pOut.print("&lt;");
}
else if (token.equals(">")) {
pOut.print("&gt;");
}
else if (token.equals("&")) {
pOut.print("&amp;");
}
else {
pOut.print(token);
}
}
}
/**
* Log a message to the servlet context.
*
* @param pMsg The error message to log.
*/
public void log(String pMsg) {
getServletContext().log(pMsg);
}
/**
* Log a message to the servlet context and include the exception that is
* passed in as the second parameter.
*
* @param pMsg The error message to log.
* @param pException The exception that caused this error message to be
* logged.
*/
public void log(String pMsg, Throwable pException) {
getServletContext().log(pMsg, pException);
}
/**
* Retrieves the ServletContext object associated with the current
* PageContext object.
*
* @return The ServletContext object associated with the current
* PageContext object.
*/
public ServletContext getServletContext() {
return pageContext.getServletContext();
}
/**
* Called when the tag has finished running. Any clean up that needs
* to be done between calls to this tag but within the same JSP page is
* called in the {@code clearServiceState()} method call.
*
* @exception JspException
*/
public int doEndTag() throws JspException {
clearServiceState();
return super.doEndTag();
}
/**
* Called when a tag's role in the current JSP page is finished. After
* the {@code clearProperties()} method is called, the custom tag
* should be in an identical state as when it was first created. The
* {@code clearServiceState()} method is called here just in case an
* exception was thrown in the custom tag. If an exception was thrown,
* then the {@code doEndTag()} method will not have been called and
* the tag might not have been cleaned up properly.
*/
public void release() {
clearServiceState();
clearProperties();
super.release();
}
/**
* The default implementation for the {@code clearProperties()}. Not
* all tags will need to overload this method call. By implementing it
* here, all classes that extend this object are able to call {@code
* super.clearProperties()}. So, if the class extends a different
* tag, or this one, the parent method should always be called. This
* method will be called when the tag is to be released. That is, the
* tag has finished for the current page and should be returned to it's
* initial state.
*/
protected void clearProperties() {
}
/**
* The default implementation for the {@code clearServiceState()}.
* Not all tags will need to overload this method call. By implementing it
* here, all classes that extend this object are able to call {@code
* super.clearServiceState()}. So, if the class extends a different
* tag, or this one, the parent method should always be called. This
* method will be called when the tag has finished it's current tag
* within the page, but may be called upon again in this same JSP page.
*/
protected void clearServiceState() {
}
/**
* Returns the initialisation parameter from the {@code
* PageContext.APPLICATION_SCOPE} scope. These initialisation
* parameters are defined in the {@code web.xml} configuration file.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @return The value for the parameter whose name was passed in as a
* parameter. If the parameter does not exist, then {@code null}
* will be returned.
*/
public String getInitParameter(String pName) {
return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
}
/**
* Returns an Enumeration containing all the names for all the
* initialisation parametes defined in the {@code
* PageContext.APPLICATION_SCOPE} scope.
*
* @return An {@code Enumeration} containing all the names for all the
* initialisation parameters.
*/
public Enumeration getInitParameterNames() {
return getInitParameterNames(PageContext.APPLICATION_SCOPE);
}
/**
* Returns the initialisation parameter from the scope specified with the
* name specified.
*
* @param pName The name of the initialisation parameter to return the
* value for.
* @param pScope The scope to search for the initialisation parameter
* within.
* @return The value of the parameter found. If no parameter with the
* name specified is found in the scope specified, then {@code null
* } is returned.
*/
public String getInitParameter(String pName, int pScope) {
switch (pScope) {
case PageContext.PAGE_SCOPE:
return getServletConfig().getInitParameter(pName);
case PageContext.APPLICATION_SCOPE:
return getServletContext().getInitParameter(pName);
default:
throw new IllegalArgumentException("Illegal scope.");
}
}
/**
* Returns an enumeration containing all the parameters defined in the
* scope specified by the parameter.
*
* @param pScope The scope to return the names of all the parameters
* defined within.
* @return An {@code Enumeration} containing all the names for all the
* parameters defined in the scope passed in as a parameter.
*/
public Enumeration getInitParameterNames(int pScope) {
switch (pScope) {
case PageContext.PAGE_SCOPE:
return getServletConfig().getInitParameterNames();
case PageContext.APPLICATION_SCOPE:
return getServletContext().getInitParameterNames();
default:
throw new IllegalArgumentException("Illegal scope");
}
}
/**
* Returns the servlet config associated with the current JSP page request.
*
* @return The {@code ServletConfig} associated with the current
* request.
*/
public ServletConfig getServletConfig() {
return pageContext.getServletConfig();
}
/**
* Gets the context path associated with the current JSP page request.
* If the request is not a HttpServletRequest, this method will
* return "/".
*
* @return a path relative to the current context's root, or
* {@code "/"} if this is not a HTTP request.
*/
public String getContextPath() {
ServletRequest request = pageContext.getRequest();
if (request instanceof HttpServletRequest) {
return ((HttpServletRequest) request).getContextPath();
}
return "/";
}
/**
* Gets the resource associated with the given relative path for the
* current JSP page request.
* The path may be absolute, or relative to the current context root.
*
* @param pPath the path
*
* @return a path relative to the current context root
*/
public InputStream getResourceAsStream(String pPath) {
//throws MalformedURLException {
String path = pPath;
if (pPath != null && !pPath.startsWith("/")) {
path = getContextPath() + pPath;
}
return pageContext.getServletContext().getResourceAsStream(path);
}
}

View File

@@ -0,0 +1,21 @@
package com.twelvemonkeys.servlet.jsp.taglib;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
/**
* TagExtraInfo for LastModifiedTag
*
* @author Harald Kuhr
*
* @version 1.1
*/
public class LastModifiedTEI extends TagExtraInfo {
public VariableInfo[] getVariableInfo(TagData pData) {
return new VariableInfo[]{
new VariableInfo("lastModified", "java.lang.String", true, VariableInfo.NESTED),
};
}
}

View File

@@ -0,0 +1,54 @@
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.File;
import java.util.Date;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import com.twelvemonkeys.util.convert.*;
/**
* Prints the last modified
*/
public class LastModifiedTag extends TagSupport {
private String mFileName = null;
private String mFormat = null;
public void setFile(String pFileName) {
mFileName = pFileName;
}
public void setFormat(String pFormat) {
mFormat = pFormat;
}
public int doStartTag() throws JspException {
File file = null;
if (mFileName != null) {
file = new File(pageContext.getServletContext()
.getRealPath(mFileName));
}
else {
HttpServletRequest request =
(HttpServletRequest) pageContext.getRequest();
// Get the file containing the servlet
file = new File(pageContext.getServletContext()
.getRealPath(request.getServletPath()));
}
Date lastModified = new Date(file.lastModified());
Converter conv = Converter.getInstance();
// Set the last modified value back
pageContext.setAttribute("lastModified",
conv.toString(lastModified, mFormat));
return Tag.EVAL_BODY_INCLUDE;
}
}

View File

@@ -0,0 +1,89 @@
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTag;
/**
* This tag truncates all consecutive whitespace in sequence inside its body,
* to one whitespace character. The first whitespace character in the sequence
* will be left untouched (except for CR/LF, which will always leave a LF).
*
* @author Harald Kuhr
*
* @version 1.0
*/
public class TrimWhiteSpaceTag extends ExBodyTagSupport {
/**
* doStartTag implementation, simply returns
* {@code BodyTag.EVAL_BODY_BUFFERED}.
*
* @return {@code BodyTag.EVAL_BODY_BUFFERED}
*/
public int doStartTag() throws JspException {
return BodyTag.EVAL_BODY_BUFFERED;
}
/**
* doEndTag implementation, truncates all whitespace.
*
* @return {@code super.doEndTag()}
*/
public int doEndTag() throws JspException {
// Trim
String trimmed = truncateWS(bodyContent.getString());
try {
// Print trimmed content
//pageContext.getOut().print("<!--TWS-->\n");
pageContext.getOut().print(trimmed);
//pageContext.getOut().print("\n<!--/TWS-->");
}
catch (IOException ioe) {
throw new JspException(ioe);
}
return super.doEndTag();
}
/**
* Truncates whitespace from the given string.
*
* @todo Candidate for StringUtil?
*/
private static String truncateWS(String pStr) {
char[] chars = pStr.toCharArray();
int count = 0;
boolean lastWasWS = true; // Avoids leading WS
for (int i = 0; i < chars.length; i++) {
if (!Character.isWhitespace(chars[i])) {
// if char is not WS, just store
chars[count++] = chars[i];
lastWasWS = false;
}
else {
// else, if char is WS, store first, skip the rest
if (!lastWasWS) {
if (chars[i] == 0x0d) {
chars[count++] = 0x0a; //Always new line
}
else {
chars[count++] = chars[i];
}
}
lastWasWS = true;
}
}
// Return the trucated string
return new String(chars, 0, count);
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright (c) 2002 TwelveMonkeys.
* All rights reserved.
*
* $Log: XMLTransformTag.java,v $
* Revision 1.2 2003/10/06 14:25:43 WMHAKUR
* Code clean-up only.
*
* Revision 1.1 2002/11/19 10:50:41 WMHAKUR
* *** empty log message ***
*
*/
package com.twelvemonkeys.servlet.jsp.taglib;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import com.twelvemonkeys.servlet.jsp.*;
/**
* This tag performs XSL Transformations (XSLT) on a given XML document or its
* body content.
*
* @author Harald Kuhr
*
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java#1 $
*/
public class XMLTransformTag extends ExBodyTagSupport {
private String mDocumentURI = null;
private String mStylesheetURI = null;
/**
* Sets the document attribute for this tag.
*/
public void setDocumentURI(String pDocumentURI) {
mDocumentURI = pDocumentURI;
}
/**
* Sets the stylesheet attribute for this tag.
*/
public void setStylesheetURI(String pStylesheetURI) {
mStylesheetURI = pStylesheetURI;
}
/**
* doStartTag implementation, that performs XML Transformation on the
* given document, if any.
* If the documentURI attribute is set, then the transformation is
* performed on the document at that location, and
* {@code Tag.SKIP_BODY} is returned.
* Otherwise, this method simply returns
* {@code BodyTag.EVAL_BODY_BUFFERED} and leaves the transformation to
* the doEndTag.
*
* @return {@code Tag.SKIP_BODY} if {@code documentURI} is not
* {@code null}, otherwise
* {@code BodyTag.EVAL_BODY_BUFFERED}.
*
* @todo Is it really a good idea to allow "inline" XML in a JSP?
*/
public int doStartTag() throws JspException {
//log("XML: " + mDocumentURI + " XSL: " + mStylesheetURI);
if (mDocumentURI != null) {
// If document given, transform and skip body...
try {
transform(getSource(mDocumentURI));
}
catch (MalformedURLException murle) {
throw new JspException(murle.getMessage(), murle);
}
catch (IOException ioe) {
throw new JspException(ioe.getMessage(), ioe);
}
return Tag.SKIP_BODY;
}
// ...else process the body
return BodyTag.EVAL_BODY_BUFFERED;
}
/**
* doEndTag implementation, that will perform XML Transformation on the
* body content.
*
* @return super.doEndTag()
*/
public int doEndTag() throws JspException {
// Get body content (trim is CRUCIAL, as some XML parsers are picky...)
String body = bodyContent.getString().trim();
// Do transformation
transform(new StreamSource(new ByteArrayInputStream(body.getBytes())));
return super.doEndTag();
}
/**
* Performs the transformation and writes the result to the JSP writer.
*
* @param in the source document to transform.
*/
public void transform(Source pIn) throws JspException {
try {
// Create transformer
Transformer transformer = TransformerFactory.newInstance()
.newTransformer(getSource(mStylesheetURI));
// Store temporary output in a bytearray, as the transformer will
// usually try to flush the stream (illegal operation from a custom
// tag).
ByteArrayOutputStream os = new ByteArrayOutputStream();
StreamResult out = new StreamResult(os);
// Perform the transformation
transformer.transform(pIn, out);
// Write the result back to the JSP writer
pageContext.getOut().print(os.toString());
}
catch (MalformedURLException murle) {
throw new JspException(murle.getMessage(), murle);
}
catch (IOException ioe) {
throw new JspException(ioe.getMessage(), ioe);
}
catch (TransformerException te) {
throw new JspException("XSLT Trandformation failed: " + te.getMessage(), te);
}
}
/**
* Returns a StreamSource object, for the given URI
*/
private StreamSource getSource(String pURI)
throws IOException, MalformedURLException {
if (pURI != null && pURI.indexOf("://") < 0) {
// If local, get as stream
return new StreamSource(getResourceAsStream(pURI));
}
// ...else, create from URI string
return new StreamSource(pURI);
}
}

View File

@@ -0,0 +1,140 @@
/****************************************************
* *
* (c) 2000-2003 TwelveMonkeys *
* All rights reserved *
* http://www.twelvemonkeys.no *
* *
* $RCSfile: ConditionalTagBase.java,v $
* @version $Revision: #1 $
* $Date: 2008/05/05 $
* *
* @author Last modified by: $Author: haku $
* *
****************************************************/
/*
* Produced (p) 2002 TwelveMonkeys
* Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
* Phone : +47 22 57 70 00
* Fax : +47 22 57 70 70
*/
package com.twelvemonkeys.servlet.jsp.taglib.logic;
import java.lang.*;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
/**
* <p>An abstract base class for tags with some kind of conditional presentation of the tag body.</p>
*
* @version 1.0
* @author <a href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</a>
*/
public abstract class ConditionalTagBase extends TagSupport {
// Members
protected String mObjectName;
protected String mObjectValue;
// Properties
/**
* Method getName
*
*
* @return
*
*/
public String getName() {
return mObjectName;
}
/**
* Method setName
*
*
* @param pObjectName
*
*/
public void setName(String pObjectName) {
this.mObjectName = pObjectName;
}
/**
* Method getValue
*
*
* @return
*
*/
public String getValue() {
return mObjectValue;
}
/**
* Method setValue
*
*
* @param pObjectValue
*
*/
public void setValue(String pObjectValue) {
this.mObjectValue = pObjectValue;
}
/**
* <p>Perform the test required for this particular tag, and either evaluate or skip the body of this tag.</p>
*
*
* @return
* @exception JspException if a JSP exception occurs.
*/
public int doStartTag() throws JspException {
if (condition()) {
return (EVAL_BODY_INCLUDE);
} else {
return (SKIP_BODY);
}
}
/**
* <p>Evaluate the remainder of the current page as normal.</p>
*
*
* @return
* @exception JspException if a JSP exception occurs.
*/
public int doEndTag() throws JspException {
return (EVAL_PAGE);
}
/**
* <p>Release all allocated resources.</p>
*/
public void release() {
super.release();
mObjectName = null;
mObjectValue = null;
}
/**
* <p>The condition that must be met in order to display the body of this tag.</p>
*
* @exception JspException if a JSP exception occurs.
* @return {@code true} if and only if all conditions are met.
*/
protected abstract boolean condition() throws JspException;
}
/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/

View File

@@ -0,0 +1,170 @@
/*
* Produced (p) 2002 TwelveMonkeys
* Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
* Phone : +47 22 57 70 00
* Fax : +47 22 57 70 70
*/
package com.twelvemonkeys.servlet.jsp.taglib.logic;
import java.lang.*;
import javax.servlet.http.Cookie;
import javax.servlet.jsp.JspException;
import com.twelvemonkeys.lang.StringUtil;
/**
* <p>
* Custom tag for testing equality of an attribute against a given value.
* The attribute types supported so far is:
* <ul>
* <li>{@code java.lang.String} (ver. 1.0)
* <li>{@code javax.servlet.http.Cookie} (ver. 1.0)
* </ul>
* </p>
* See the implemented <a href="#condition">{@code condition}</a> method for details regarding the equality conditions.
*
* <p><hr></p>
*
* <h3>Tag Reference</h3>
* <table border="0" cellspacing="3" cellpadding="3" width="90%">
* <tr bgcolor="#cccccc">
* <td colspan="5" class="body"><b>equal</b></td>
* <td width="17%" align="right" class="body">Availability:&nbsp;1.0</td>
* </tr>
* <tr>
* <td colspan="6" class="body"><p>Tag for testing if an attribute is equal to a given value.</p></td>
* </tr>
* <tr>
* <td width="15%" class="body"><b>Tag Body</b></td>
* <td width="17%" class="body">JSP</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* </tr>
* <tr>
* <td class="body"><b>Restrictions</b></td>
* <td colspan="5" class="body"><p>None</p></td>
* </tr>
*
* <tr>
* <td class="body"><b>Attributes</b></td>
* <td class="body">Name</td>
* <td class="body">Required</td>
* <td colspan="2" class="body">Runtime&nbsp;Expression&nbsp;Evaluation</td>
* <td class="body">Availability</td>
* </tr>
*
* <tr bgcolor="#cccccc">
* <td bgcolor="#ffffff">&nbsp;</td>
* <td class="body_grey"><b>name</b></td>
* <td class="body_grey">&nbsp;Yes</td>
* <td colspan="2" class="body_grey">&nbsp;Yes</td>
* <td class="body_grey">&nbsp;1.0</td>
* </tr>
* <tr>
* <td bgcolor="#ffffff">&nbsp;</td>
* <td colspan="5" class="body"><p>The attribute name</p></td>
* </tr>
*
* <tr bgcolor="#cccccc">
* <td bgcolor="#ffffff">&nbsp;</td>
* <td class="body_grey"><b>value</b></td>
* <td class="body_grey">&nbsp;No</td>
* <td colspan="2" class="body_grey">&nbsp;Yes</td>
* <td class="body_grey">&nbsp;1.0</td>
* </tr>
* <tr>
* <td bgcolor="#ffffff" class="body">&nbsp;</td>
* <td colspan="5" class="body"><p>The value for equality testing</p></td>
* </tr>
*
* <tr>
* <td class="body" valign="top"><b>Variables</b></td>
* <td colspan="5" class="body">None</td>
* </tr>
*
* <tr>
* <td class="body" valign="top"><b>Examples</b></td>
* <td colspan="5" class="body">
* <pre>
*&lt;%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %&gt;
*&lt;bean:cookie id="logonUsernameCookie"
* name="&lt;%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %&gt;"
* value="no_username_set" /&gt;
*&lt;twelvemonkeys:equal name="logonUsernameCookie" value="no_username_set"&gt;
* &lt;html:text property="username" /&gt;
*&lt;/twelvemonkeys:equal&gt;
* </pre>
* </td>
* </tr>
* </table>
*
* <hr>
*
* @version 1.0
* @author <a href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</a>
* @see <a href="NotEqualTag.html">notEqual</a>
*/
public class EqualTag extends ConditionalTagBase {
/**
* <a name="condition"></a>
*
* The conditions that must be met in order to display the body of this tag:
* <ol>
* <li>The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
* <li>The attribute must exist.
* <li>The attribute must be an instance of one of the supported classes:
* <ul>
* <li>{@code java.lang.String}
* <li>{@code javax.servlet.http.Cookie}
* </ul>
* <li>The value of the attribute must be equal to the object value property ({@code value} -> {@code mObjectValue}).
* </ol>
* <p>
* NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
* </p>
*
* @return {@code true} if and only if all conditions are met.
*/
protected boolean condition() throws JspException {
if (StringUtil.isEmpty(mObjectName)) {
return false;
}
if (StringUtil.isEmpty(mObjectValue)) {
return true;
}
Object pageScopedAttribute = pageContext.getAttribute(mObjectName);
if (pageScopedAttribute == null) {
return false;
}
String pageScopedStringAttribute;
// String
if (pageScopedAttribute instanceof String) {
pageScopedStringAttribute = (String) pageScopedAttribute;
// Cookie
}
else if (pageScopedAttribute instanceof Cookie) {
pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
// Type not yet supported...
}
else {
return false;
}
return (pageScopedStringAttribute.equals(mObjectValue));
}
}

View File

@@ -0,0 +1,41 @@
package com.twelvemonkeys.servlet.jsp.taglib.logic;
import javax.servlet.jsp.tagext.*;
/**
* TagExtraInfo class for IteratorProvider tags.
*
* @author Harald Kuhr
* @version $id: $
*/
public class IteratorProviderTEI extends TagExtraInfo {
/**
* Gets the variable info for IteratorProvider tags. The attribute with the
* name defined by the "id" attribute and type defined by the "type"
* attribute is declared with scope {@code VariableInfo.AT_END}.
*
* @param pData TagData instance provided by container
* @return an VariableInfo array of lenght 1, containing the attribute
* defined by the id parameter, declared, and with scope
* {@code VariableInfo.AT_END}.
*/
public VariableInfo[] getVariableInfo(TagData pData) {
// Get attribute name
String attributeName = pData.getId();
if (attributeName == null) {
attributeName = IteratorProviderTag.getDefaultIteratorName();
}
// Get type
String type = pData.getAttributeString(IteratorProviderTag.ATTRIBUTE_TYPE);
if (type == null) {
type = IteratorProviderTag.getDefaultIteratorType();
}
// Return the variable info
return new VariableInfo[]{
new VariableInfo(attributeName, type, true, VariableInfo.AT_END),
};
}
}

View File

@@ -0,0 +1,87 @@
package com.twelvemonkeys.servlet.jsp.taglib.logic;
import java.util.Iterator;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.*;
/**
* Abstract base class for adding iterators to a page.
*
* @todo Possible to use same strategy for all types of objects? Rename class
* to ObjectProviderTag? Hmmm... Might work.
*
* @author Harald Kuhr
* @version $id: $
*/
public abstract class IteratorProviderTag extends TagSupport {
/** {@code iterator} */
protected final static String DEFAULT_ITERATOR_NAME = "iterator";
/** {@code java.util.iterator} */
protected final static String DEFAULT_ITERATOR_TYPE = "java.util.Iterator";
/** {@code type} */
public final static String ATTRIBUTE_TYPE = "type";
/** */
private String mType = null;
/**
* Gets the type.
*
* @return the type (class name)
*/
public String getType() {
return mType;
}
/**
* Sets the type.
*
* @param pType
*/
public void setType(String pType) {
mType = pType;
}
/**
* doEndTag implementation.
*
* @return {@code Tag.EVAL_PAGE}
* @throws JspException
*/
public int doEndTag() throws JspException {
// Set the iterator
pageContext.setAttribute(getId(), getIterator());
return Tag.EVAL_PAGE;
}
/**
* Gets the iterator for this tag.
*
* @return an {@link java.util.Iterator}
*/
protected abstract Iterator getIterator();
/**
* Gets the default iterator name.
*
* @return {@link #DEFAULT_ITERATOR_NAME}
*/
protected static String getDefaultIteratorName() {
return DEFAULT_ITERATOR_NAME;
}
/**
* Gets the default iterator type.
*
* @return {@link #DEFAULT_ITERATOR_TYPE}
*/
protected static String getDefaultIteratorType() {
return DEFAULT_ITERATOR_TYPE;
}
}

View File

@@ -0,0 +1,168 @@
/*
* Produced (p) 2002 TwelveMonkeys
* Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
* Phone : +47 22 57 70 00
* Fax : +47 22 57 70 70
*/
package com.twelvemonkeys.servlet.jsp.taglib.logic;
import com.twelvemonkeys.lang.StringUtil;
import javax.servlet.http.Cookie;
import javax.servlet.jsp.JspException;
/**
* <p>
* Custom tag for testing non-equality of an attribute against a given value.
* The attribute types supported so far is:
* <ul>
* <li>{@code java.lang.String} (ver. 1.0)
* <li>{@code javax.servlet.http.Cookie} (ver. 1.0)
* </ul>
* </p>
* See the implemented <a href="#condition">{@code condition}</a> method for details regarding the non-equality conditions.
*
* <p><hr></p>
*
* <h3>Tag Reference</h3>
* <table border="0" cellspacing="3" cellpadding="3" width="90%">
* <tr bgcolor="#cccccc">
* <td colspan="5" class="body"><b>notEqual</b></td>
* <td width="17%" align="right" class="body">Availability:&nbsp;1.0</td>
* </tr>
* <tr>
* <td colspan="6" class="body"><p>Tag for testing if an attribute is NOT equal to a given value.</p></td>
* </tr>
* <tr>
* <td width="15%" class="body"><b>Tag Body</b></td>
* <td width="17%" class="body">JSP</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* <td width="17%" class="body">&nbsp;</td>
* </tr>
* <tr>
* <td class="body"><b>Restrictions</b></td>
* <td colspan="5" class="body"><p>None</p></td>
* </tr>
*
* <tr>
* <td class="body"><b>Attributes</b></td>
* <td class="body">Name</td>
* <td class="body">Required</td>
* <td colspan="2" class="body">Runtime&nbsp;Expression&nbsp;Evaluation</td>
* <td class="body">Availability</td>
* </tr>
*
* <tr bgcolor="#cccccc">
* <td bgcolor="#ffffff">&nbsp;</td>
* <td class="body_grey"><b>name</b></td>
* <td class="body_grey">&nbsp;Yes</td>
* <td colspan="2" class="body_grey">&nbsp;Yes</td>
* <td class="body_grey">&nbsp;1.0</td>
* </tr>
* <tr>
* <td bgcolor="#ffffff">&nbsp;</td>
* <td colspan="5" class="body"><p>The attribute name</p></td>
* </tr>
*
* <tr bgcolor="#cccccc">
* <td bgcolor="#ffffff">&nbsp;</td>
* <td class="body_grey"><b>value</b></td>
* <td class="body_grey">&nbsp;No</td>
* <td colspan="2" class="body_grey">&nbsp;Yes</td>
* <td class="body_grey">&nbsp;1.0</td>
* </tr>
* <tr>
* <td bgcolor="#ffffff" class="body">&nbsp;</td>
* <td colspan="5" class="body"><p>The value for equality testing</p></td>
* </tr>
*
* <tr>
* <td class="body" valign="top"><b>Variables</b></td>
* <td colspan="5" class="body">None</td>
* </tr>
*
* <tr>
* <td class="body" valign="top"><b>Examples</b></td>
* <td colspan="5" class="body">
* <pre>
*&lt;%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %&gt;
*&lt;bean:cookie id="logonUsernameCookie"
* name="&lt;%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %&gt;"
* value="no_username_set" /&gt;
*&lt;twelvemonkeys:notEqual name="logonUsernameCookie" value="no_username_set"&gt;
* &lt;html:text property="username" value="&lt;%= logonUsernameCookie.getValue() %&gt;" /&gt;
*&lt;/twelvemonkeys:notEqual&gt;
* </pre>
* </td>
* </tr>
* </table>
*
* <hr>
*
* @version 1.0
* @author <a href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</a>
* @see <a href="EqualTag.html">equal</a>
*/
public class NotEqualTag extends ConditionalTagBase {
/**
* <a name="condition"></a>
*
* The condition that must be met in order to display the body of this tag:
* <ol>
* <li>The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
* <li>The attribute must exist.
* <li>The attribute must be an instance of one of the supported classes:
* <ul>
* <li>{@code java.lang.String}
* <li>{@code javax.servlet.http.Cookie}
* </ul>
* <li>The value of the attribute must NOT be equal to the object value property ({@code value} -> {@code mObjectValue}).
* </ol>
* <p>
* NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
* </p>
*
* @return {@code true} if and only if all conditions are met.
*/
protected boolean condition() throws JspException {
if (StringUtil.isEmpty(mObjectName)) {
return false;
}
if (StringUtil.isEmpty(mObjectValue)) {
return true;
}
Object pageScopedAttribute = pageContext.getAttribute(mObjectName);
if (pageScopedAttribute == null) {
return false;
}
String pageScopedStringAttribute;
// String
if (pageScopedAttribute instanceof String) {
pageScopedStringAttribute = (String) pageScopedAttribute;
// Cookie
}
else if (pageScopedAttribute instanceof Cookie) {
pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
// Type not yet supported...
}
else {
return false;
}
return (!(pageScopedStringAttribute.equals(mObjectValue)));
}
}

View File

@@ -0,0 +1,4 @@
/**
* The TwelveMonkeys common TagLib.
*/
package com.twelvemonkeys.servlet.jsp.taglib;

View File

@@ -0,0 +1,183 @@
package com.twelvemonkeys.servlet.log4j;
import org.apache.log4j.Logger;
import java.util.Enumeration;
import java.util.Set;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.InputStream;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import javax.servlet.ServletContext;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
/**
* Log4JContextWrapper
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java#1 $
*/
final class Log4JContextWrapper implements ServletContext {
// TODO: This solution sucks...
// How about starting to create some kind of pluggable decorator system,
// something along the lines of AOP mixins/interceptor pattern..
// Probably using a dynamic Proxy, delegating to the mixins and or the
// wrapped object based on configuration.
// This way we could simply call ServletUtil.decorate(ServletContext):ServletContext
// And the context would be decorated with all configured mixins at once,
// requiring less bolierplate delegation code, and less layers of wrapping
// (alternatively we could decorate the Servlet/FilterConfig objects).
// See the ServletUtil.createWrapper methods for some hints..
// Something like this:
public static ServletContext wrap(final ServletContext pContext, final Object[] pDelegates, final ClassLoader pLoader) {
ClassLoader cl = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
// TODO: Create a "static" mapping between methods in the ServletContext
// and the corresponding delegate
// TODO: Resolve super-invokations, to delegate to next delegate in
// chain, and finally invoke pContext
return (ServletContext) Proxy.newProxyInstance(cl, new Class[] {ServletContext.class}, new InvocationHandler() {
public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable {
// TODO: Test if any of the delegates should receive, if so invoke
// Else, invoke on original object
return pMethod.invoke(pContext, pArgs);
}
});
}
private final ServletContext mContext;
private final Logger mLogger;
Log4JContextWrapper(ServletContext pContext) {
mContext = pContext;
// TODO: We want a logger per servlet, not per servlet context, right?
mLogger = Logger.getLogger(pContext.getServletContextName());
// TODO: Automatic init/config of Log4J using context parameter for log4j.xml?
// See Log4JInit.java
// TODO: Automatic config of properties in the context wrapper?
}
public final void log(final Exception pException, final String pMessage) {
log(pMessage, pException);
}
// TODO: Add more logging methods to interface info/warn/error?
// TODO: Implement these mehtods in GenericFilter/GenericServlet?
public void log(String pMessage) {
// TODO: Get logger for caller..
// Should be possible using some stack peek hack, but that's slow...
// Find a good way...
// Maybe just pass it into the constuctor, and have one wrapper per servlet
mLogger.info(pMessage);
}
public void log(String pMessage, Throwable pCause) {
// TODO: Get logger for caller..
mLogger.error(pMessage, pCause);
}
public Object getAttribute(String pMessage) {
return mContext.getAttribute(pMessage);
}
public Enumeration getAttributeNames() {
return mContext.getAttributeNames();
}
public ServletContext getContext(String pMessage) {
return mContext.getContext(pMessage);
}
public String getInitParameter(String pMessage) {
return mContext.getInitParameter(pMessage);
}
public Enumeration getInitParameterNames() {
return mContext.getInitParameterNames();
}
public int getMajorVersion() {
return mContext.getMajorVersion();
}
public String getMimeType(String pMessage) {
return mContext.getMimeType(pMessage);
}
public int getMinorVersion() {
return mContext.getMinorVersion();
}
public RequestDispatcher getNamedDispatcher(String pMessage) {
return mContext.getNamedDispatcher(pMessage);
}
public String getRealPath(String pMessage) {
return mContext.getRealPath(pMessage);
}
public RequestDispatcher getRequestDispatcher(String pMessage) {
return mContext.getRequestDispatcher(pMessage);
}
public URL getResource(String pMessage) throws MalformedURLException {
return mContext.getResource(pMessage);
}
public InputStream getResourceAsStream(String pMessage) {
return mContext.getResourceAsStream(pMessage);
}
public Set getResourcePaths(String pMessage) {
return mContext.getResourcePaths(pMessage);
}
public String getServerInfo() {
return mContext.getServerInfo();
}
public Servlet getServlet(String pMessage) throws ServletException {
//noinspection deprecation
return mContext.getServlet(pMessage);
}
public String getServletContextName() {
return mContext.getServletContextName();
}
public Enumeration getServletNames() {
//noinspection deprecation
return mContext.getServletNames();
}
public Enumeration getServlets() {
//noinspection deprecation
return mContext.getServlets();
}
public void removeAttribute(String pMessage) {
mContext.removeAttribute(pMessage);
}
public void setAttribute(String pMessage, Object pExtension) {
mContext.setAttribute(pMessage, pExtension);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Contains servlet support classes.
*/
package com.twelvemonkeys.servlet;

View File

@@ -0,0 +1,438 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.lang.ObjectAbstractTestCase;
import java.util.*;
import java.net.URL;
import java.net.MalformedURLException;
import java.io.*;
import javax.servlet.*;
/**
* FilterAbstractTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java#1 $
*/
public abstract class FilterAbstractTestCase extends ObjectAbstractTestCase {
protected Object makeObject() {
return makeFilter();
}
protected abstract Filter makeFilter();
// TODO: Is it a good thing to have an API like this?
protected FilterConfig makeFilterConfig() {
return makeFilterConfig(new HashMap());
}
protected FilterConfig makeFilterConfig(Map pParams) {
return new MockFilterConfig(pParams);
}
protected ServletRequest makeRequest() {
return new MockServletRequest();
}
protected ServletResponse makeResponse() {
return new MockServletResponse();
}
protected FilterChain makeFilterChain() {
return new MockFilterChain();
}
public void testInitNull() {
Filter filter = makeFilter();
// The spec seems to be a little unclear on this issue, but anyway,
// the container should never invoke init(null)...
try {
filter.init(null);
fail("Should throw Exception on init(null)");
}
catch (IllegalArgumentException e) {
// Good
}
catch (NullPointerException e) {
// Bad (but not unreasonable)
}
catch (ServletException e) {
// Hmmm.. The jury is still out.
}
}
public void testInit() {
Filter filter = makeFilter();
try {
filter.init(makeFilterConfig());
}
catch (ServletException e) {
assertNotNull(e.getMessage());
}
finally {
filter.destroy();
}
}
public void testLifeCycle() throws ServletException {
Filter filter = makeFilter();
try {
filter.init(makeFilterConfig());
}
finally {
filter.destroy();
}
}
public void testFilterBasic() throws ServletException, IOException {
Filter filter = makeFilter();
try {
filter.init(makeFilterConfig());
filter.doFilter(makeRequest(), makeResponse(), makeFilterChain());
}
finally {
filter.destroy();
}
}
public void testDestroy() {
// TODO: Implement
}
static class MockFilterConfig implements FilterConfig {
private final Map mParams;
MockFilterConfig() {
this(new HashMap());
}
MockFilterConfig(Map pParams) {
if (pParams == null) {
throw new IllegalArgumentException("params == null");
}
mParams = pParams;
}
public String getFilterName() {
return "mock-filter";
}
public String getInitParameter(String pName) {
return (String) mParams.get(pName);
}
public Enumeration getInitParameterNames() {
return Collections.enumeration(mParams.keySet());
}
public ServletContext getServletContext() {
return new MockServletContext();
}
private static class MockServletContext implements ServletContext {
private final Map mAttributes;
private final Map mParams;
MockServletContext() {
mAttributes = new HashMap();
mParams = new HashMap();
}
public Object getAttribute(String s) {
return mAttributes.get(s);
}
public Enumeration getAttributeNames() {
return Collections.enumeration(mAttributes.keySet());
}
public ServletContext getContext(String s) {
return null; // TODO: Implement
}
public String getInitParameter(String s) {
return (String) mParams.get(s);
}
public Enumeration getInitParameterNames() {
return Collections.enumeration(mParams.keySet());
}
public int getMajorVersion() {
return 0; // TODO: Implement
}
public String getMimeType(String s) {
return null; // TODO: Implement
}
public int getMinorVersion() {
return 0; // TODO: Implement
}
public RequestDispatcher getNamedDispatcher(String s) {
return null; // TODO: Implement
}
public String getRealPath(String s) {
return null; // TODO: Implement
}
public RequestDispatcher getRequestDispatcher(String s) {
return null; // TODO: Implement
}
public URL getResource(String s) throws MalformedURLException {
return null; // TODO: Implement
}
public InputStream getResourceAsStream(String s) {
return null; // TODO: Implement
}
public Set getResourcePaths(String s) {
return null; // TODO: Implement
}
public String getServerInfo() {
return null; // TODO: Implement
}
public Servlet getServlet(String s) throws ServletException {
return null; // TODO: Implement
}
public String getServletContextName() {
return "mock";
}
public Enumeration getServletNames() {
return null; // TODO: Implement
}
public Enumeration getServlets() {
return null; // TODO: Implement
}
public void log(Exception exception, String s) {
// TODO: Implement
}
public void log(String s) {
// TODO: Implement
}
public void log(String s, Throwable throwable) {
// TODO: Implement
}
public void removeAttribute(String s) {
mAttributes.remove(s);
}
public void setAttribute(String s, Object obj) {
mAttributes.put(s, obj);
}
}
}
static class MockServletRequest implements ServletRequest {
final private Map mAttributes;
public MockServletRequest() {
mAttributes = new HashMap();
}
public Object getAttribute(String pKey) {
return mAttributes.get(pKey);
}
public Enumeration getAttributeNames() {
return Collections.enumeration(mAttributes.keySet());
}
public String getCharacterEncoding() {
return null; // TODO: Implement
}
public void setCharacterEncoding(String pMessage) throws UnsupportedEncodingException {
// TODO: Implement
}
public int getContentLength() {
return 0; // TODO: Implement
}
public String getContentType() {
return null; // TODO: Implement
}
public ServletInputStream getInputStream() throws IOException {
return null; // TODO: Implement
}
public String getParameter(String pMessage) {
return null; // TODO: Implement
}
public Enumeration getParameterNames() {
return null; // TODO: Implement
}
public String[] getParameterValues(String pMessage) {
return new String[0]; // TODO: Implement
}
public Map getParameterMap() {
return null; // TODO: Implement
}
public String getProtocol() {
return null; // TODO: Implement
}
public String getScheme() {
return null; // TODO: Implement
}
public String getServerName() {
return null; // TODO: Implement
}
public int getServerPort() {
return 0; // TODO: Implement
}
public BufferedReader getReader() throws IOException {
return null; // TODO: Implement
}
public String getRemoteAddr() {
return null; // TODO: Implement
}
public String getRemoteHost() {
return null; // TODO: Implement
}
public void setAttribute(String pKey, Object pValue) {
mAttributes.put(pKey, pValue);
}
public void removeAttribute(String pKey) {
mAttributes.remove(pKey);
}
public Locale getLocale() {
return null; // TODO: Implement
}
public Enumeration getLocales() {
return null; // TODO: Implement
}
public boolean isSecure() {
return false; // TODO: Implement
}
public RequestDispatcher getRequestDispatcher(String pMessage) {
return null; // TODO: Implement
}
public String getRealPath(String pMessage) {
return null; // TODO: Implement
}
public int getRemotePort() {
throw new UnsupportedOperationException("Method getRemotePort not implemented");// TODO: Implement
}
public String getLocalName() {
throw new UnsupportedOperationException("Method getLocalName not implemented");// TODO: Implement
}
public String getLocalAddr() {
throw new UnsupportedOperationException("Method getLocalAddr not implemented");// TODO: Implement
}
public int getLocalPort() {
throw new UnsupportedOperationException("Method getLocalPort not implemented");// TODO: Implement
}
}
static class MockServletResponse implements ServletResponse {
public void flushBuffer() throws IOException {
// TODO: Implement
}
public int getBufferSize() {
return 0; // TODO: Implement
}
public String getCharacterEncoding() {
return null; // TODO: Implement
}
public String getContentType() {
throw new UnsupportedOperationException("Method getContentType not implemented");// TODO: Implement
}
public Locale getLocale() {
return null; // TODO: Implement
}
public ServletOutputStream getOutputStream() throws IOException {
return null; // TODO: Implement
}
public PrintWriter getWriter() throws IOException {
return null; // TODO: Implement
}
public void setCharacterEncoding(String charset) {
throw new UnsupportedOperationException("Method setCharacterEncoding not implemented");// TODO: Implement
}
public boolean isCommitted() {
return false; // TODO: Implement
}
public void reset() {
// TODO: Implement
}
public void resetBuffer() {
// TODO: Implement
}
public void setBufferSize(int pLength) {
// TODO: Implement
}
public void setContentLength(int pLength) {
// TODO: Implement
}
public void setContentType(String pMessage) {
// TODO: Implement
}
public void setLocale(Locale pLocale) {
// TODO: Implement
}
}
static class MockFilterChain implements FilterChain {
public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
// TODO: Implement
}
}
}

View File

@@ -0,0 +1,151 @@
package com.twelvemonkeys.servlet;
import java.io.IOException;
import java.util.Map;
import java.util.HashMap;
import javax.servlet.*;
/**
* GenericFilterTestCase
* <p/>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java#1 $
*/
public final class GenericFilterTestCase extends FilterAbstractTestCase {
protected Filter makeFilter() {
return new GenericFilterImpl();
}
public void testInitOncePerRequest() {
// Default FALSE
GenericFilter filter = new GenericFilterImpl();
try {
filter.init(makeFilterConfig());
}
catch (ServletException e) {
fail(e.getMessage());
}
assertFalse("OncePerRequest should default to false", filter.mOncePerRequest);
filter.destroy();
// TRUE
filter = new GenericFilterImpl();
Map params = new HashMap();
params.put("once-per-request", "true");
try {
filter.init(makeFilterConfig(params));
}
catch (ServletException e) {
fail(e.getMessage());
}
assertTrue("oncePerRequest should be true", filter.mOncePerRequest);
filter.destroy();
// TRUE
filter = new GenericFilterImpl();
params = new HashMap();
params.put("oncePerRequest", "true");
try {
filter.init(makeFilterConfig(params));
}
catch (ServletException e) {
fail(e.getMessage());
}
assertTrue("oncePerRequest should be true", filter.mOncePerRequest);
filter.destroy();
}
public void testFilterOnlyOnce() {
final GenericFilterImpl filter = new GenericFilterImpl();
filter.setOncePerRequest(true);
try {
filter.init(makeFilterConfig());
}
catch (ServletException e) {
fail(e.getMessage());
}
FilterChain chain = new MyFilterChain(new Filter[] {filter, filter, filter});
try {
chain.doFilter(makeRequest(), makeResponse());
}
catch (IOException e) {
fail(e.getMessage());
}
catch (ServletException e) {
fail(e.getMessage());
}
assertEquals("Filter was invoked more than once!", 1, filter.invocationCount);
filter.destroy();
}
public void testFilterMultiple() {
final GenericFilterImpl filter = new GenericFilterImpl();
try {
filter.init(makeFilterConfig());
}
catch (ServletException e) {
fail(e.getMessage());
}
FilterChain chain = new MyFilterChain(new Filter[] {
filter, filter, filter, filter, filter
});
try {
chain.doFilter(makeRequest(), makeResponse());
}
catch (IOException e) {
fail(e.getMessage());
}
catch (ServletException e) {
fail(e.getMessage());
}
assertEquals("Filter was invoked not invoked five times!", 5, filter.invocationCount);
filter.destroy();
}
private static class GenericFilterImpl extends GenericFilter {
int invocationCount;
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
invocationCount++;
pChain.doFilter(pRequest, pResponse);
}
}
private static class MyFilterChain implements FilterChain {
Filter[] mFilters;
int mCurrentFilter;
public MyFilterChain(Filter[] pFilters) {
if (pFilters == null) {
throw new IllegalArgumentException("filters == null");
}
mFilters = pFilters;
mCurrentFilter = 0;
}
public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
if (mCurrentFilter < mFilters.length) {
mFilters[mCurrentFilter++].doFilter(pRequest, pResponse, this);
}
}
}
}

View File

@@ -0,0 +1,93 @@
package com.twelvemonkeys.servlet;
import com.twelvemonkeys.io.NullOutputStream;
import junit.framework.TestCase;
import java.io.PrintWriter;
/**
* ServletConfigExceptionTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigExceptionTestCase.java#2 $
*/
public class ServletConfigExceptionTestCase extends TestCase {
public void testThrowCatchPrintStacktrace() {
try {
throw new ServletConfigException("FooBar!");
}
catch (ServletConfigException e) {
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
public void testThrowCatchGetNoCause() {
try {
throw new ServletConfigException("FooBar!");
}
catch (ServletConfigException e) {
assertEquals(null, e.getRootCause()); // Old API
assertEquals(null, e.getCause());
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
public void testThrowCatchInitCauseNull() {
try {
ServletConfigException e = new ServletConfigException("FooBar!");
e.initCause(null);
throw e;
}
catch (ServletConfigException e) {
assertEquals(null, e.getRootCause()); // Old API
assertEquals(null, e.getCause());
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
public void testThrowCatchInitCause() {
//noinspection ThrowableInstanceNeverThrown
Exception cause = new Exception();
try {
ServletConfigException exception = new ServletConfigException("FooBar!");
exception.initCause(cause);
throw exception;
}
catch (ServletConfigException e) {
// NOTE: We don't know how the superclass is implemented, so we assume nothing here
//assertEquals(null, e.getRootCause()); // Old API
assertSame(cause, e.getCause());
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
public void testThrowCatchGetNullCause() {
try {
throw new ServletConfigException("FooBar!", null);
}
catch (ServletConfigException e) {
assertEquals(null, e.getRootCause()); // Old API
assertEquals(null, e.getCause());
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
public void testThrowCatchGetCause() {
IllegalStateException cause = new IllegalStateException();
try {
throw new ServletConfigException("FooBar caused by stupid API!", cause);
}
catch (ServletConfigException e) {
assertSame(cause, e.getRootCause()); // Old API
assertSame(cause, e.getCause());
e.printStackTrace(new PrintWriter(new NullOutputStream()));
}
}
}

Some files were not shown because too many files have changed in this diff Show More