Servlet project

This commit is contained in:
Harald Kuhr
2009-09-03 21:13:12 +02:00
parent 16b588fde5
commit 87baad3f99
109 changed files with 18537 additions and 0 deletions

95
twelvemonkeys-servlet/pom.xml Executable file
View File

@@ -0,0 +1,95 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-servlet</artifactId>
<version>2.1</version>
<name>TwelveMonkeys Servlet</name>
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-parent</artifactId>
<version>2.0</version>
</parent>
<properties>
<core.version>2.1</core.version>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-core</artifactId>
<version>${core.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys-core</artifactId>
<version>${core.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>
</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,747 @@
/*
* 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 allready set
if (mOriginalContentType != null) {
throw new IllegalStateException("ContentType allready 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();
}
// This is stupid, but don't know how to work around...
// TODO: Test what types of images that work with JPEG, consider reporting it as a bug
if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType)
|| "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) &&
mImage instanceof BufferedImage && ((BufferedImage) mImage).getType() == BufferedImage.TYPE_INT_ARGB) {
mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB);
}
//System.out.println("Writing image, content-type: " + getContentType(outputType));
//System.out.println("Writing image, outputType: " + outputType);
//System.out.println("Writing image: " + mImage);
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);
//System.out.println("-ISR- Image: " + mImage);
//System.out.println("-ISR- ImageWriter: " + writer);
//System.out.println("-ISR- ImageOutputStream: " + stream);
writer.setOutput(stream);
try {
writer.write(null, new IIOImage(mImage, null, null), param);
}
finally {
stream.close();
}
}
finally {
writer.dispose();
out.flush();
// out.close();
}
}
else {
mContext.log("ERROR: No writer for content-type: " + outputType);
// sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to encode image: 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);
//System.out.println("-ISR- Image: " + 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,38 @@
<HTML>
<BODY>
Contains various image-outputting servlets, that should run under any servlet engine. To create your own image servlet, simply subclass the servlet
<CODE>ImageServlet</CODE>. Optionally implement the interface
<CODE>ImagePainterServlet</CODE>, 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</CODE></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;</CODE></DD>
<DD><CODE>-Dawt.toolkit=com.eteks.awt.PJAToolkit</CODE></DD>
<DD><CODE>-Djava.awt.graphicsenv=com.eteks.java2d.PJAGraphicsEnvironment</CODE></DD>
<DD><CODE>-Djava.awt.fonts=&lt;path where True Type fonts files will be loaded from&gt;</CODE></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
</BODY>
</HTML>

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,14 @@
<HTML>
<BODY>
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?
</BODY>
</HTML>

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,7 @@
<HTML>
<BODY>
JSP
</BODY>
</HTML>

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,7 @@
<HTML>
<BODY>
The TwelveMonkeys common TagLib.
</BODY>
</HTML>

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,7 @@
<HTML>
<BODY>
Contains servlet support classes.
</BODY>
</HTML>

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