From seam-commits at lists.jboss.org Tue Aug 25 04:13:37 2009 Content-Type: multipart/mixed; boundary="===============6559507730917790534==" MIME-Version: 1.0 From: seam-commits at lists.jboss.org To: seam-commits at lists.jboss.org Subject: [seam-commits] Seam SVN: r11419 - in branches/community/Seam_2_2: src/main/org/jboss/seam and 3 other directories. Date: Tue, 25 Aug 2009 04:13:36 -0400 Message-ID: <200908250813.n7P8Dag2024389@svn01.web.mwc.hst.phx2.redhat.com> --===============6559507730917790534== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Author: christian.bauer(a)jboss.com Date: 2009-08-25 04:13:36 -0400 (Tue, 25 Aug 2009) New Revision: 11419 Added: branches/community/Seam_2_2/src/main/org/jboss/seam/web/CacheControlFilt= er.java branches/community/Seam_2_2/src/main/org/jboss/seam/web/ConditionalAbstr= actResource.java branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/web/C= onditionalRequestTest.java Modified: branches/community/Seam_2_2/doc/Seam_Reference_Guide/en-US/Configuration= .xml branches/community/Seam_2_2/src/main/org/jboss/seam/web-2.2.xsd branches/community/Seam_2_2/src/main/org/jboss/seam/web/AbstractResource= .java branches/community/Seam_2_2/src/test/unit/org/jboss/seam/test/unit/testn= g.xml Log: JBSEAM-4383, infrastructure for HTTP optimization Modified: branches/community/Seam_2_2/doc/Seam_Reference_Guide/en-US/Config= uration.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/doc/Seam_Reference_Guide/en-US/Configuratio= n.xml 2009-08-24 13:41:30 UTC (rev 11418) +++ branches/community/Seam_2_2/doc/Seam_Reference_Guide/en-US/Configuratio= n.xml 2009-08-25 08:13:36 UTC (rev 11419) @@ -374,8 +374,36 @@ = = - = + Enabling HTTP cache-control headers + + Seam does not automatically add <= literal>cache-control HTTP headers to + any resources served by the Seam resource servlet, or = directly from your view directory by the servlet + container. This means that your images, Javascript and= CSS files, and resource representations from + Seam resource servlet such as Seam Remoting Javascript= interfaces are usually not cached by the browser. + This is convenient in development but should be change= d in production when optimizing the application. + + + + You can configure a Seam filter to enable automatic ad= dition of cache-control headers + depending on the requested URI in components.= xml: + + + + + ]]><= /programlisting> + + + You do not have to name the filters unless you have mo= re than one filter enabled. + + + + + Adding custom filters 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, 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==--