You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mr...@apache.org on 2007/10/27 12:47:25 UTC

svn commit: r589071 - in /struts/sandbox/trunk/struts2-rest-plugin: ./ src/main/java/org/apache/struts2/rest/ src/test/java/org/apache/struts2/rest/

Author: mrdon
Date: Sat Oct 27 03:47:25 2007
New Revision: 589071

URL: http://svn.apache.org/viewvc?rev=589071&view=rev
Log:
Adding automatic support for conditional gets

Added:
    struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/DefaultHttpHeadersTest.java
Modified:
    struts/sandbox/trunk/struts2-rest-plugin/pom.xml
    struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/ContentTypeHandlerManager.java
    struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/DefaultHttpHeaders.java
    struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/ContentTypeHandlerManagerTest.java

Modified: struts/sandbox/trunk/struts2-rest-plugin/pom.xml
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-rest-plugin/pom.xml?rev=589071&r1=589070&r2=589071&view=diff
==============================================================================
--- struts/sandbox/trunk/struts2-rest-plugin/pom.xml (original)
+++ struts/sandbox/trunk/struts2-rest-plugin/pom.xml Sat Oct 27 03:47:25 2007
@@ -53,6 +53,13 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-mock</artifactId>
+            <version>1.2.8</version>
+            <optional>true</optional>
+        </dependency>
+
     </dependencies>
 
 </project>

Modified: struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/ContentTypeHandlerManager.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/ContentTypeHandlerManager.java?rev=589071&r1=589070&r2=589071&view=diff
==============================================================================
--- struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/ContentTypeHandlerManager.java (original)
+++ struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/ContentTypeHandlerManager.java Sat Oct 27 03:47:25 2007
@@ -29,6 +29,7 @@
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
 import static javax.servlet.http.HttpServletResponse.SC_OK;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -101,13 +102,20 @@
         if (target instanceof ModelDriven) {
             target = ((ModelDriven)target).getModel();
         }
-        
+
         boolean statusNotOk = false;
         if (methodResult instanceof HttpHeaders) {
             HttpHeaders info = (HttpHeaders) methodResult;
             resultCode = info.apply(req, res, target);
             if (info.getStatus() != SC_OK) {
-                statusNotOk = true;
+
+                // Don't return content on a not modified
+                if (info.getStatus() == SC_NOT_MODIFIED) {
+                    target = null;
+                } else {
+                    statusNotOk = true;
+                }
+
             }
         } else {
             resultCode = (String) methodResult;
@@ -117,20 +125,22 @@
         if (!statusNotOk && !"get".equalsIgnoreCase(req.getMethod())) {
             target = null;
         }
-        
+
         ContentTypeHandler handler = getHandlerForRequest(req);
-        String extCode = resultCode+"-"+handler.getExtension();
-        if (actionConfig.getResults().get(extCode) != null) {
-            resultCode = extCode;
-        } else {
-            ByteArrayOutputStream bout = new ByteArrayOutputStream();
-            
-            resultCode = handler.fromObject(target, resultCode, bout);
-            if (bout.size() > 0) {
-                res.setContentLength(bout.size());
-                res.setContentType(handler.getContentType());
-                res.getOutputStream().write(bout.toByteArray());
-                res.getOutputStream().close();
+        if (handler != null) {
+            String extCode = resultCode+"-"+handler.getExtension();
+            if (actionConfig.getResults().get(extCode) != null) {
+                resultCode = extCode;
+            } else {
+                ByteArrayOutputStream bout = new ByteArrayOutputStream();
+
+                resultCode = handler.fromObject(target, resultCode, bout);
+                if (bout.size() > 0) {
+                    res.setContentLength(bout.size());
+                    res.setContentType(handler.getContentType());
+                    res.getOutputStream().write(bout.toByteArray());
+                    res.getOutputStream().close();
+                }
             }
         }
         return resultCode;

Modified: struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/DefaultHttpHeaders.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/DefaultHttpHeaders.java?rev=589071&r1=589070&r2=589071&view=diff
==============================================================================
--- struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/DefaultHttpHeaders.java (original)
+++ struts/sandbox/trunk/struts2-rest-plugin/src/main/java/org/apache/struts2/rest/DefaultHttpHeaders.java Sat Oct 27 03:47:25 2007
@@ -20,12 +20,11 @@
  */
 package org.apache.struts2.rest;
 
-import static javax.servlet.http.HttpServletResponse.*;
-
-import java.util.Date;
-
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import java.util.Date;
 
 /**
  * Default implementation of rest info that uses fluent-style construction
@@ -37,6 +36,7 @@
     Object locationId;
     String location;
     boolean disableCaching;
+    boolean noETag = false;
     Date lastModified;
     
     public DefaultHttpHeaders renderResult(String code) {
@@ -53,6 +53,11 @@
         this.etag = etag;
         return this;
     }
+
+    public DefaultHttpHeaders withNoETag() {
+        this.noETag = true;
+        return this;
+    }
     
     public DefaultHttpHeaders setLocationId(Object id) {
         this.locationId = id;
@@ -78,30 +83,60 @@
      * @see org.apache.struts2.rest.HttpHeaders#apply(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
      */
     public String apply(HttpServletRequest request, HttpServletResponse response, Object target) {
-        response.setStatus(status);
+
         if (disableCaching) {
             response.setHeader("Cache-Control", "no-cache");
-        } else if (lastModified != null) {
-            response.setDateHeader("LastModified", lastModified.getTime());
-        } else {
-            if (etag == null) {
-                etag = String.valueOf(target.hashCode());
-            }
+        }
+        if (lastModified != null) {
+            response.setDateHeader("Last-Modified", lastModified.getTime());
+        }
+        if (etag == null && !noETag) {
+            etag = String.valueOf(target.hashCode());
+        }
+        if (etag != null) {
             response.setHeader("ETag", etag.toString());
         }
+
         if (locationId != null) {
             String url = request.getRequestURL().toString();
             int lastSlash = url.lastIndexOf("/");
             int lastDot = url.lastIndexOf(".");
             if (lastDot > lastSlash && lastDot > -1) {
-                url = url.substring(0, lastDot)+locationId+url.substring(lastDot);
+                url = url.substring(0, lastDot)+"/"+locationId+url.substring(lastDot);
             } else {
-                url += locationId;
+                url += "/"+locationId;
             }
             response.setHeader("Location", url);
         } else if (location != null) {
             response.setHeader("Location", location);
         }
+
+        if (status == SC_OK) {
+            boolean etagNotChanged = false;
+            boolean lastModifiedNotChanged = false;
+            String reqETag = request.getHeader("If-None-Match");
+            if (etag != null) {
+                if (etag.equals(reqETag)) {
+                    etagNotChanged = true;
+                }
+            }
+
+            String reqLastModified = request.getHeader("If-Modified-Since");
+            if (lastModified != null) {
+                if (String.valueOf(lastModified.getTime()).equals(reqLastModified)) {
+                    lastModifiedNotChanged = true;
+                }
+
+            }
+
+            if ((etagNotChanged && lastModifiedNotChanged) ||
+                (etagNotChanged && reqLastModified == null) ||
+                (lastModifiedNotChanged && reqETag == null)) {
+                status = SC_NOT_MODIFIED;
+            }
+        }
+
+        response.setStatus(status);
         return resultCode;
     }
 

Modified: struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/ContentTypeHandlerManagerTest.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/ContentTypeHandlerManagerTest.java?rev=589071&r1=589070&r2=589071&view=diff
==============================================================================
--- struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/ContentTypeHandlerManagerTest.java (original)
+++ struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/ContentTypeHandlerManagerTest.java Sat Oct 27 03:47:25 2007
@@ -22,15 +22,77 @@
 
 import com.mockobjects.dynamic.C;
 import com.mockobjects.dynamic.Mock;
+import com.opensymphony.xwork2.ActionContext;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
 import com.opensymphony.xwork2.inject.Container;
 import junit.framework.TestCase;
+import org.apache.struts2.ServletActionContext;
 import org.apache.struts2.rest.handler.ContentTypeHandler;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
 
+import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 
 public class ContentTypeHandlerManagerTest extends TestCase {
+
+    private ContentTypeHandlerManager mgr;
+    private MockHttpServletResponse mockResponse;
+    private MockHttpServletRequest mockRequest;
+
+    @Override
+    public void setUp() {
+        mgr = new ContentTypeHandlerManager();
+        mockResponse = new MockHttpServletResponse();
+        mockRequest = new MockHttpServletRequest();
+        mockRequest.setMethod("GET");
+        ActionContext.setContext(new ActionContext(new HashMap()));
+        ServletActionContext.setRequest(mockRequest);
+        ServletActionContext.setResponse(mockResponse);
+    }
+
+    @Override
+    public void tearDown() {
+        mockRequest = null;
+        mockRequest = null;
+        mgr = null;
+    }
+
+    public void testHandleResultOK() throws IOException {
+
+        String obj = "mystring";
+        ContentTypeHandler handler = new ContentTypeHandler() {
+            public void toObject(InputStream in, Object target) {}
+            public String fromObject(Object obj, String resultCode, OutputStream stream) throws IOException {
+                stream.write(obj.toString().getBytes());
+                return resultCode;
+            }
+            public String getContentType() { return "foo"; }
+            public String getExtension() { return "foo"; }
+        };
+        mgr.handlers.put("xml", handler);
+        mgr.defaultHandlerName = "xml";
+        mgr.handleResult(new ActionConfig(), new DefaultHttpHeaders().withStatus(SC_OK), obj);
+
+        assertEquals(obj.getBytes().length, mockResponse.getContentLength());
+    }
+
+    public void testHandleResultNotModified() throws IOException {
+
+        Mock mockHandlerXml = new Mock(ContentTypeHandler.class);
+        mockHandlerXml.matchAndReturn("getExtension", "xml");
+        mgr.handlers.put("xml", (ContentTypeHandler) mockHandlerXml.proxy());
+        mgr.handleResult(null, new DefaultHttpHeaders().withStatus(SC_NOT_MODIFIED), new Object());
+
+        assertEquals(0, mockResponse.getContentLength());
+    }
 
     public void testHandlerOverride() {
         Mock mockHandlerXml = new Mock(ContentTypeHandler.class);

Added: struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/DefaultHttpHeadersTest.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/DefaultHttpHeadersTest.java?rev=589071&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/DefaultHttpHeadersTest.java (added)
+++ struts/sandbox/trunk/struts2-rest-plugin/src/test/java/org/apache/struts2/rest/DefaultHttpHeadersTest.java Sat Oct 27 03:47:25 2007
@@ -0,0 +1,175 @@
+/*
+ * $Id: Restful2ActionMapper.java 540819 2007-05-23 02:48:36Z mrdon $
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.struts2.rest;
+
+import junit.framework.TestCase;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static javax.servlet.http.HttpServletResponse.*;
+import java.util.Date;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: mrdon
+ * Date: 27/10/2007
+ * Time: 18:14:48
+ * To change this template use File | Settings | File Templates.
+ */
+public class DefaultHttpHeadersTest extends TestCase {
+    private MockHttpServletResponse mockResponse;
+    private MockHttpServletRequest mockRequest;
+
+    @Override
+    public void setUp() {
+        mockResponse = new MockHttpServletResponse();
+        mockRequest = new MockHttpServletRequest();
+    }
+
+    @Override
+    public void tearDown() {
+        mockRequest = null;
+        mockRequest = null;
+    }
+
+    public void testApply() {
+        Date now = new Date();
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .lastModified(now)
+                .withStatus(SC_OK)
+                .setLocationId("44")
+                .withETag("asdf");
+        mockRequest.setRequestURI("/foo/bar.xhtml");
+
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_OK, mockResponse.getStatus());
+        assertEquals("http://localhost:80/foo/bar/44.xhtml", mockResponse.getHeader("Location"));
+        assertEquals("asdf", mockResponse.getHeader("ETag"));
+        assertEquals(now.getTime(), mockResponse.getHeader("Last-Modified"));
+
+    }
+
+    public void testApplyNoLocationExtension() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .setLocationId("44");
+        mockRequest.setRequestURI("/foo/bar");
+
+        headers.apply(mockRequest, mockResponse, new Object());
+        assertEquals("http://localhost:80/foo/bar/44", mockResponse.getHeader("Location"));
+
+    }
+
+    public void testApplyFullLocation() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .setLocation("http://localhost/bar/44");
+        mockRequest.setRequestURI("/foo/bar");
+
+        headers.apply(mockRequest, mockResponse, new Object());
+        assertEquals("http://localhost/bar/44", mockResponse.getHeader("Location"));
+
+    }
+
+    public void testAutoETag() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders();
+        headers.apply(mockRequest, mockResponse, new Object() {
+            @Override
+            public int hashCode() {
+                return 123;
+            }
+        });
+
+        assertEquals("123", mockResponse.getHeader("ETag"));
+    }
+
+    public void testNoCache() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .disableCaching();
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals("no-cache", mockResponse.getHeader("Cache-Control"));
+    }
+
+    public void testConditionalGetForJustETag() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .withETag("asdf");
+        mockRequest.addHeader("If-None-Match", "asdf");
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
+        assertEquals("asdf", mockResponse.getHeader("ETag"));
+    }
+
+    public void testConditionalGetForJustETagNotOK() {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .withETag("asdf")
+                .withStatus(SC_BAD_REQUEST);
+        mockRequest.addHeader("If-None-Match", "asdf");
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_BAD_REQUEST, mockResponse.getStatus());
+        assertEquals("asdf", mockResponse.getHeader("ETag"));
+    }
+
+    public void testConditionalGetForJustLastModified() {
+        Date now = new Date();
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .lastModified(now);
+        mockRequest.addHeader("If-Modified-Since", String.valueOf(now.getTime()));
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
+    }
+
+    public void testConditionalGetForJustLastModifiedDifferent() {
+        Date now = new Date();
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .lastModified(now);
+        mockRequest.addHeader("If-Modified-Since", String.valueOf(new Date(2323L).getTime()));
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_OK, mockResponse.getStatus());
+    }
+
+    public void testConditionalGetForLastModifiedAndETag() {
+        Date now = new Date();
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .lastModified(now)
+                .withETag("asdf");
+        mockRequest.addHeader("If-None-Match", "asdf");
+        mockRequest.addHeader("If-Modified-Since", String.valueOf(now.getTime()));
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_NOT_MODIFIED, mockResponse.getStatus());
+    }
+
+    public void testConditionalGetForLastModifiedAndETagWithBadETag() {
+        Date now = new Date();
+        DefaultHttpHeaders headers = new DefaultHttpHeaders()
+                .lastModified(now)
+                .withETag("fdsa");
+        mockRequest.addHeader("If-None-Match", "asdfds");
+        mockRequest.addHeader("If-Modified-Since", String.valueOf(now.getTime()));
+        headers.apply(mockRequest, mockResponse, new Object());
+
+        assertEquals(SC_OK, mockResponse.getStatus());
+    }
+}