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());
+ }
+}