Seam can install your filters for you, allowing you=
to specify where in the
chain your filter is placed (the servlet specification=
doesn't provide a well defined order if you
Modified: branches/community/Seam_2_2/src/main/org/jboss/seam/web/AbstractR=
esource.java
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/main/org/jboss/seam/web/AbstractResourc=
e.java 2009-08-24 13:41:30 UTC (rev 11418)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/web/AbstractResourc=
e.java 2009-08-25 08:13:36 UTC (rev 11419)
@@ -1,42 +1,177 @@
package org.jboss.seam.web;
=
-import java.io.IOException;
-
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.zip.GZIPOutputStream;
=
/**
- * Superclass of Seam components that serve up =
- * "resources" to the client via the Seam =
+ * Superclass of Seam components that serve up
+ * "resources" to the client via the Seam
* resource servlet. Note that since a filter is
* potentially called outside of a set of Seam
- * contexts, it is not a true Seam component. =
+ * contexts, it is not a true Seam component.
* However, we are able to reuse the functionality
- * for component scanning, installation and =
+ * for component scanning, installation and
* configuration for filters. All resources
* must extend this class.
- * =
+ *
* @author Shane Bryzak
*
*/
public abstract class AbstractResource
{
private ServletContext context;
- =
+
protected ServletContext getServletContext()
{
return context;
}
- =
+
public void setServletContext(ServletContext context)
{
this.context =3D context;
}
- =
+
public abstract void getResource(HttpServletRequest request, HttpServle=
tResponse response)
throws ServletException, IOException;
- =
+
public abstract String getResourcePath();
+
+ protected OutputStream selectOutputStream(HttpServletRequest request, H=
ttpServletResponse response)
+ throws IOException
+ {
+
+ String acceptEncoding =3D request.getHeader("Accept-Encoding");
+ String mimeType =3D response.getContentType();
+
+ if (isGzipEnabled()
+ && acceptEncoding !=3D null
+ && acceptEncoding.length() > 0
+ && acceptEncoding.indexOf("gzip") > -1
+ && isCompressedMimeType(mimeType))
+ {
+ return new GZIPResponseStream(response);
+ }
+ else
+ {
+ return response.getOutputStream();
+ }
+ }
+
+ protected boolean isCompressedMimeType(String mimeType)
+ {
+ return mimeType.matches("text/.+");
+ }
+
+ protected boolean isGzipEnabled()
+ {
+ return true;
+ }
+
+ /*
+ * Copyright 2004-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impl=
ied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @See org/springframework/js/resource/ResourceServlet.java
+ */
+ private class GZIPResponseStream extends ServletOutputStream
+ {
+
+ private ByteArrayOutputStream byteStream =3D null;
+
+ private GZIPOutputStream gzipStream =3D null;
+
+ private boolean closed =3D false;
+
+ private HttpServletResponse response =3D null;
+
+ private ServletOutputStream servletStream =3D null;
+
+ public GZIPResponseStream(HttpServletResponse response) throws IOExc=
eption
+ {
+ super();
+ closed =3D false;
+ this.response =3D response;
+ this.servletStream =3D response.getOutputStream();
+ byteStream =3D new ByteArrayOutputStream();
+ gzipStream =3D new GZIPOutputStream(byteStream);
+ }
+
+ public void close() throws IOException
+ {
+ if (closed)
+ {
+ throw new IOException("This output stream has already been clo=
sed");
+ }
+ gzipStream.finish();
+
+ byte[] bytes =3D byteStream.toByteArray();
+
+ response.setContentLength(bytes.length);
+ response.addHeader("Content-Encoding", "gzip");
+ servletStream.write(bytes);
+ servletStream.flush();
+ servletStream.close();
+ closed =3D true;
+ }
+
+ public void flush() throws IOException
+ {
+ if (closed)
+ {
+ throw new IOException("Cannot flush a closed output stream");
+ }
+ gzipStream.flush();
+ }
+
+ public void write(int b) throws IOException
+ {
+ if (closed)
+ {
+ throw new IOException("Cannot write to a closed output stream"=
);
+ }
+ gzipStream.write((byte) b);
+ }
+
+ public void write(byte b[]) throws IOException
+ {
+ write(b, 0, b.length);
+ }
+
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ if (closed)
+ {
+ throw new IOException("Cannot write to a closed output stream"=
);
+ }
+ gzipStream.write(b, off, len);
+ }
+
+ public boolean closed()
+ {
+ return (this.closed);
+ }
+
+ public void reset()
+ {
+ // noop
+ }
+ }
}
Added: branches/community/Seam_2_2/src/main/org/jboss/seam/web/CacheControl=
Filter.java
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/main/org/jboss/seam/web/CacheControlFil=
ter.java (rev 0)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/web/CacheControlFil=
ter.java 2009-08-25 08:13:36 UTC (rev 11419)
@@ -0,0 +1,64 @@
+package org.jboss.seam.web;
+
+import org.jboss.seam.ScopeType;
+import org.jboss.seam.log.Logging;
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.annotations.Install;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.intercept.BypassInterceptors;
+import org.jboss.seam.annotations.web.Filter;
+
+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;
+
+/**
+ * Provides automatic addition of cache-control HTTP headers to matching r=
esource responses.
+ *
+ * @author Christian Bauer
+ */
+(a)Scope(ScopeType.APPLICATION)
+(a)Name("org.jboss.seam.web.cacheControlFilter")
+(a)Install(value =3D false, precedence =3D Install.BUILT_IN)
+(a)BypassInterceptors
+(a)Filter(within =3D "org.jboss.seam.web.exceptionFilter")
+public class CacheControlFilter extends AbstractFilter
+{
+
+ private static final LogProvider log =3D Logging.getLogProvider(CacheCo=
ntrolFilter.class);
+
+ private String value;
+
+ public void doFilter(ServletRequest request, ServletResponse response, =
FilterChain chain)
+ throws IOException, ServletException
+ {
+
+ HttpServletRequest httpRequest =3D (HttpServletRequest) request;
+
+ if (isMappedToCurrentRequestPath(request))
+ {
+ log.debug("Applying Cache-Control HTTP header for resource '"
+ + httpRequest.getRequestURI() + "': " + getValue());
+
+ HttpServletResponse httpResponse =3D (HttpServletResponse) respon=
se;
+ httpResponse.setHeader("Cache-Control", getValue());
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ public String getValue()
+ {
+ return value;
+ }
+
+ public void setValue(String value)
+ {
+ this.value =3D value;
+ }
+}
Added: branches/community/Seam_2_2/src/main/org/jboss/seam/web/ConditionalA=
bstractResource.java
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/main/org/jboss/seam/web/ConditionalAbst=
ractResource.java (rev 0)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/web/ConditionalAbst=
ractResource.java 2009-08-25 08:13:36 UTC (rev 11419)
@@ -0,0 +1,281 @@
+package org.jboss.seam.web;
+
+import org.jboss.seam.log.LogProvider;
+import org.jboss.seam.log.Logging;
+import org.jboss.seam.util.Resources;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.net.URLConnection;
+import java.net.URL;
+import java.lang.management.ManagementFactory;
+
+/**
+ * Subclass this resource if you want to be able to send the right respons=
e automatically to
+ * any conditional GET or HEAD request. The typically us=
ecase is as follows:
+ *
+ *
+ * public class MyResource extends ConditionalAbstractResource {
+ *
+ * public void getResource(final HttpServletRequest request, final Htt=
pServletResponse response) {
+ * String resourceVersion =3D ... // Calculate current state as st=
ring
+ * or
+ * byte[] resourceVersion =3D ... // Calculate current state as by=
tes
+ *
+ * String resourcePath =3D ... // Get the relative (to servlet) pa=
th of the requested resource
+ *
+ * if ( !sendConditional(request,
+ * response,
+ * createdEntityTag(resourceVersion, false),
+ * getLastModifiedTimestamp(resourcePath) ) {
+ * =
+ * // Send the regular resource representation with 200 OK etc.
+ * }
+ * }
+ * }
+ *
+ *
+ * Note that the getLastModifiedTimestamp() method is only suppli=
ed for convenience; it may not
+ * return what you expect as the "last modification timestamp" of the give=
n resource. In many cases you'd
+ * rather calculate that timestamp yourself.
+ *
+ *
+ * @author Christian Bauer
+ */
+public abstract class ConditionalAbstractResource extends AbstractResource
+{
+
+ public static final String HEADER_LAST_MODIFIED =3D "Last-Modified";
+ public static final String HEADER_IF_MODIFIED_SINCE =3D "If-Modified-Si=
nce";
+
+ public static final String HEADER_ETAG =3D "ETag";
+ public static final String HEADER_IF_NONE_MATCH =3D "If-None-Match";
+
+ private static final LogProvider log =3D Logging.getLogProvider(Conditi=
onalAbstractResource.class);
+
+ /**
+ * Validates the request headers If-Modified-Since and If-=
None-Match to determine
+ * if a 304 NOT MODIFIED response can be send. If that is the =
case, this method will automatically
+ * send the response and return true. If condition validation =
fails, it will not change the
+ * response and return false.
+ *
+ * Note that both entityTag and lastModified argument=
s can be null. The validation
+ * procedure and the outcome depends on what the client requested. If t=
he client requires that both entity tags and
+ * modification timestamps be validated, both arguments must be supplie=
d to the method and they must match, for
+ * a 304 response to be send.
+ *
+ * In addition to responding with 304 NOT MODIFIED when condit=
ions match, this method will also, if
+ * arguments are not null, send the right entity tag and last =
modification timestamps with the response,
+ * so that future requests from the client can be made conditional.
+ *
+ *
+ * @param request The usual HttpServletRequest for header retri=
eval.
+ * @param response The usual HttpServletResponse for header mani=
pulation.
+ * @param entityTag An entity tag (weak or strong, in doublequote=
s), typically produced by hashing the content
+ * of the resource representation. If null=
tt>, no entity tag will be send and if
+ * validation is requested by the client, no mat=
ch for a NOT MODIFIED response will be possible.
+ * @param lastModified The timestamp in number of milliseconds since=
unix epoch when the resource was
+ * last modified. If null, no last modi=
fication timestamp will be send and if
+ * validation is requested by the client, no mat=
ch for a NOT MODIFIED response will be possible.
+ * @return true if a 304 NOT MODIFIED response status=
has been set, false if requested
+ * conditions were invalid given the current state of the resou=
rce.
+ * @throws IOException If setting the response status failed.
+ */
+ public boolean sendConditional(HttpServletRequest request,
+ HttpServletResponse response,
+ String entityTag, Long lastModified) thr=
ows IOException
+ {
+
+ String noneMatchHeader =3D request.getHeader(HEADER_IF_NONE_MATCH);
+ Long modifiedSinceHeader =3D request.getDateHeader(HEADER_IF_MODIFIE=
D_SINCE); // Careful, returns -1 instead of null!
+
+ boolean noneMatchValid =3D false;
+ if (entityTag !=3D null)
+ {
+
+ if (! (entityTag.startsWith("\"") || entityTag.startsWith("W/\"")=
) && !entityTag.endsWith("\""))
+ {
+ throw new IllegalArgumentException("Entity tag is not properly=
formatted (or quoted): " + entityTag);
+ }
+
+ // Always send an entity tag with the response
+ response.setHeader(HEADER_ETAG, entityTag);
+
+ if (noneMatchHeader !=3D null)
+ {
+ noneMatchValid =3D isNoneMatchConditionValid(noneMatchHeader, =
entityTag);
+ }
+ }
+
+ boolean modifiedSinceValid =3D false;
+ if (lastModified !=3D null)
+ {
+
+ // Always send the last modified timestamp with the response
+ response.setDateHeader(HEADER_LAST_MODIFIED, lastModified);
+
+ if (modifiedSinceHeader !=3D -1)
+ {
+ modifiedSinceValid =3D isModifiedSinceConditionValid(modifiedS=
inceHeader, lastModified);
+ }
+
+ }
+
+ if (noneMatchHeader !=3D null && modifiedSinceHeader !=3D -1)
+ {
+ log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_SIN=
CE + " must match");
+
+ // If both are received, we must not return 304 unless doing so i=
s consistent with both header fields in the request!
+ if (noneMatchValid && modifiedSinceValid)
+ {
+ log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_=
SINCE + " conditions match, sending 304");
+ response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+ return true;
+ }
+ else
+ {
+ log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_=
SINCE + " conditions do not match, not sending 304");
+ return false;
+ }
+ }
+
+ if (noneMatchHeader !=3D null && noneMatchValid)
+ {
+ log.debug(HEADER_IF_NONE_MATCH + " condition matches, sending 304=
");
+ response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+ return true;
+ }
+
+ if (modifiedSinceHeader !=3D -1 && modifiedSinceValid)
+ {
+ log.debug(HEADER_IF_MODIFIED_SINCE + " condition matches, sending=
304");
+ response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+ return true;
+ }
+
+ log.debug("None of the cache conditions match, not sending 304");
+ return false;
+ }
+
+ protected boolean isNoneMatchConditionValid(String noneMatchHeader, Str=
ing entityTag)
+ {
+ if (noneMatchHeader.trim().equals("*"))
+ {
+ log.debug("Found * conditional request, hence current entity tag =
matches");
+ return true;
+ }
+ String[] entityTagsArray =3D noneMatchHeader.trim().split(",");
+ for (String requestTag : entityTagsArray)
+ {
+ if (requestTag.trim().equals(entityTag))
+ {
+ log.debug("Found matching entity tag in request");
+ return true;
+ }
+ }
+ log.debug("Resource has different entity tag than requested");
+ return false;
+ }
+
+ protected boolean isModifiedSinceConditionValid(Long modifiedSinceHeade=
r, Long lastModified)
+ {
+ if (lastModified <=3D modifiedSinceHeader)
+ {
+ log.debug("Resource has not been modified since requested timesta=
mp");
+ return true;
+ }
+ log.debug("Resource has been modified since requested timestamp");
+ return false;
+ }
+
+ /**
+ * Tries to get last modification timestamp of the resource by obtaining
+ * a URLConnection to the file in the filesystem or JAR.
+ *
+ * @param resourcePath The relative (to the servlet) resource path.
+ * @return Either the last modified filestamp or if an error occurs, th=
e JVM system startup timestamp.
+ */
+ protected Long getLastModifiedTimestamp(String resourcePath)
+ {
+ try
+ {
+ // Try to load it from filesystem or JAR through URLConnection
+ URL resourceURL =3D Resources.getResource(resourcePath, getServle=
tContext());
+ if (resourceURL =3D=3D null)
+ {
+ // Fall back to startup time of the JVM
+ return ManagementFactory.getRuntimeMXBean().getStartTime();
+ }
+ URLConnection resourceConn =3D resourceURL.openConnection();
+ return resourceConn.getLastModified();
+ }
+ catch (Exception ex)
+ {
+ // Fall back to startup time of the JVM
+ return ManagementFactory.getRuntimeMXBean().getStartTime();
+ }
+ }
+
+ /**
+ * Generates a (globally) unique identifier of the current state of the=
resource. The string will be
+ * hashed with MD5 and the hash result is then formatted before it is r=
eturned. If null,
+ * a null will be returned.
+ *
+ * @param hashSource The string source for hashing or the already hashe=
d (strong or weak) entity tag.
+ * @param weak Set to true if you want a weak entity tag.
+ * @return The hashed and formatted entity tag result.
+ */
+ protected String createEntityTag(String hashSource, boolean weak)
+ {
+ if (hashSource =3D=3D null) return null;
+ return (weak ? "W/\"" : "\"") + hash(hashSource, "UTF-8", "MD5") + "=
\"";
+ }
+
+ /**
+ * Generates a (globally) unique identifier of the current state of the=
resource. The bytes will be
+ * hashed with MD5 and the hash result is then formatted before it is r=
eturned. If null,
+ * a null will be returned.
+ *
+ * @param hashSource The string source for hashing.
+ * @param weak Set to true if you want a weak entity tag.
+ * @return The hashed and formatted entity tag result.
+ */
+ protected String createEntityTag(byte[] hashSource, boolean weak)
+ {
+ if (hashSource =3D=3D null) return null;
+ return (weak ? "W/\"" : "\"") + hash(hashSource, "MD5") + "\"";
+ }
+
+ protected String hash(String text, String charset, String algorithm)
+ {
+ try
+ {
+ return hash(text.getBytes(charset), algorithm);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected String hash(byte[] bytes, String algorithm)
+ {
+ try
+ {
+ MessageDigest md =3D MessageDigest.getInstance(algorithm);
+ md.update(bytes);
+ BigInteger number =3D new BigInteger(1, md.digest());
+ StringBuffer sb =3D new StringBuffer("0");
+ sb.append(number.toString(16));
+ return sb.toString();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
Modified: branches/community/Seam_2_2/src/main/org/jboss/seam/web-2.2.xsd
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/main/org/jboss/seam/web-2.2.xsd 2009-08=
-24 13:41:30 UTC (rev 11418)
+++ branches/community/Seam_2_2/src/main/org/jboss/seam/web-2.2.xsd 2009-08=
-25 08:13:36 UTC (rev 11419)
@@ -185,4 +185,15 @@
=
+
+
+ Sets the HTTP Cache-Control header
+
+
+
+
+
+
+
+
Modified: branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/uni=
t/testng.xml
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/test=
ng.xml 2009-08-24 13:41:30 UTC (rev 11418)
+++ branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/test=
ng.xml 2009-08-25 08:13:36 UTC (rev 11419)
@@ -70,6 +70,7 @@
+
=
Added: branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/w=
eb/ConditionalRequestTest.java
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
--- branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/web/=
ConditionalRequestTest.java (rev 0)
+++ branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/web/=
ConditionalRequestTest.java 2009-08-25 08:13:36 UTC (rev 11419)
@@ -0,0 +1,228 @@
+package org.jboss.seam.test.unit.web;
+
+import org.testng.annotations.Test;
+import org.testng.Assert;
+import static org.testng.Assert.assertEquals;
+import org.jboss.seam.mock.MockHttpSession;
+import org.jboss.seam.mock.EnhancedMockHttpServletRequest;
+import org.jboss.seam.mock.EnhancedMockHttpServletResponse;
+import org.jboss.seam.web.ConditionalAbstractResource;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * @author Christian Bauer
+ *
+ */
+public class ConditionalRequestTest
+{
+ @Test
+ public void testNotModifiedOnlyETag() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_NONE_MATCH, =
"\"1234\", \"5678\"");
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, "\"5678\"", null))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_NOT_MODIFI=
ED);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_E=
TAG), "\"5678\"");
+
+ }
+
+ @Test
+ public void testModifiedOnlyETag() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_NONE_MATCH, =
"\"123\", \"456\"");
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, "\"5678\"", null))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_OK);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_E=
TAG), "\"5678\"");
+ }
+
+ @Test
+ public void testNotModifiedOnlyLastModified() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ final Long currentTime =3D new Date().getTime();
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_MODIFIED_SIN=
CE, currentTime);
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, null, currentTime))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_NOT_MODIFI=
ED);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_L=
AST_MODIFIED), currentTime);
+
+ }
+
+ @Test
+ public void testModifiedOnlyLastModified() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ final Long currentTime =3D new Date().getTime();
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_MODIFIED_SIN=
CE, currentTime);
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, null, currentTime + 50=
00))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_OK);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_L=
AST_MODIFIED), currentTime + 5000);
+
+ }
+
+ @Test
+ public void testNotModifiedETagLastModified() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ final Long currentTime =3D new Date().getTime();
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_MODIFIED_SIN=
CE, currentTime);
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_NONE_MATCH, =
"\"1234\", \"5678\"");
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, "\"5678\"", currentTim=
e))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_NOT_MODIFI=
ED);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_L=
AST_MODIFIED), currentTime);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_E=
TAG), "\"5678\"");
+
+ }
+
+ @Test
+ public void testModifiedETagLastModified() throws Exception
+ {
+
+ HttpSession session =3D new MockHttpSession();
+ EnhancedMockHttpServletRequest request =3D new EnhancedMockHttpServl=
etRequest(session);
+ EnhancedMockHttpServletResponse response =3D new EnhancedMockHttpSer=
vletResponse();
+
+ final Long currentTime =3D new Date().getTime();
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_MODIFIED_SIN=
CE, currentTime);
+ request.addHeader(ConditionalAbstractResource.HEADER_IF_NONE_MATCH, =
"\"1234\", \"5678\"");
+
+ ConditionalAbstractResource resource =3D new ConditionalAbstractReso=
urce()
+ {
+ public void getResource(HttpServletRequest request, HttpServletRe=
sponse response) throws ServletException, IOException
+ {
+ if (!sendConditional(request, response, "\"5678\"", currentTim=
e + 5000))
+ {
+ response.sendError(HttpServletResponse.SC_OK);
+ }
+ }
+
+ public String getResourcePath()
+ {
+ return null;
+ }
+ };
+
+ resource.getResource(request, response);
+
+ assertEquals(response.getStatus(), HttpServletResponse.SC_OK);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_L=
AST_MODIFIED), currentTime + 5000);
+ assertEquals(response.getHeader(ConditionalAbstractResource.HEADER_E=
TAG), "\"5678\"");
+
+ }
+}
--===============6559507730917790534==--