TUSCANY-2477 - Applying Dan's patch that addes support for eTag and last-modified headers

git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@684294 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
lresende 2008-08-09 16:50:30 +00:00
parent f1d53b3e8a
commit 365a5ce268
6 changed files with 1024 additions and 22 deletions
java/sca/modules/binding-atom-abdera
pom.xml
src
main/java/org/apache/tuscany/sca/binding/atom/provider
test/java/org/apache/tuscany/sca/binding/atom

View file

@ -81,7 +81,7 @@
<dependency>
<groupId>org.apache.abdera</groupId>
<artifactId>abdera-core</artifactId>
<version>0.3.0-incubating</version>
<version>0.4.0-incubating</version>
<exclusions>
<exclusion>
<groupId>org.apache.geronimo.specs</groupId>
@ -93,7 +93,7 @@
<dependency>
<groupId>org.apache.abdera</groupId>
<artifactId>abdera-parser</artifactId>
<version>0.3.0-incubating</version>
<version>0.4.0-incubating</version>
<exclusions>
<exclusion>
<groupId>stax</groupId>
@ -105,6 +105,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.abdera</groupId>
<artifactId>abdera-client</artifactId>
<version>0.4.0-incubating</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>

View file

@ -86,7 +86,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Get an entry
String id = (String)((Object[])msg.getBody())[0];
@ -100,7 +99,8 @@ class AtomBindingInvoker implements Invoker {
// Read the Atom entry
if (status == 200) {
Document<org.apache.abdera.model.Entry> doc = abderaParser.parse(new InputStreamReader(getMethod.getResponseBodyAsStream()));
Document<org.apache.abdera.model.Entry> doc =
abderaParser.parse(new InputStreamReader(getMethod.getResponseBodyAsStream()));
parsing = true;
org.apache.abdera.model.Entry feedEntry = doc.getRoot();
@ -148,7 +148,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Post an entry
Object[] args = (Object[])msg.getBody();
org.apache.abdera.model.Entry feedEntry;
@ -173,7 +172,8 @@ class AtomBindingInvoker implements Invoker {
// Write the Atom entry
StringWriter writer = new StringWriter();
feedEntry.writeTo(writer);
postMethod.setRequestHeader("Content-type", "application/atom+xml; charset=utf-8");
// postMethod.setRequestHeader("Content-type", "application/atom+xml; charset=utf-8");
postMethod.setRequestHeader("Content-type", "application/atom+xml;type=entry");
postMethod.setRequestEntity(new StringRequestEntity(writer.toString()));
httpClient.executeMethod(postMethod);
@ -228,7 +228,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Put an entry
Object[] args = (Object[])msg.getBody();
String id;
@ -250,6 +249,7 @@ class AtomBindingInvoker implements Invoker {
// Send an HTTP PUT
PutMethod putMethod = new PutMethod(uri + "/" + id);
putMethod.setRequestHeader("Authorization", authorizationHeader);
try {
// Write the Atom entry
@ -260,7 +260,7 @@ class AtomBindingInvoker implements Invoker {
httpClient.executeMethod(putMethod);
int status = putMethod.getStatusCode();
if (status == 200 || status == 201) {
if (status == 200 || status == 201 || status == 412) {
msg.setBody(null);
@ -291,7 +291,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Delete an entry
String id = (String)((Object[])msg.getBody())[0];
@ -331,7 +330,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Get a feed
// Send an HTTP GET
@ -341,6 +339,7 @@ class AtomBindingInvoker implements Invoker {
try {
httpClient.executeMethod(getMethod);
int status = getMethod.getStatusCode();
// AtomBindingInvoker.printResponseHeader( getMethod );
// Read the Atom feed
if (status == 200) {
@ -396,7 +395,6 @@ class AtomBindingInvoker implements Invoker {
@Override
public Message invoke(Message msg) {
// Get a feed from a query
String queryString = (String)((Object[])msg.getBody())[0];

View file

@ -26,6 +26,8 @@ import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.StringTokenizer;
import java.util.logging.Logger;
@ -37,6 +39,7 @@ import javax.xml.namespace.QName;
import org.apache.abdera.Abdera;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.model.Collection;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Feed;
@ -58,7 +61,6 @@ import org.apache.tuscany.sca.invocation.Message;
import org.apache.tuscany.sca.invocation.MessageFactory;
import org.apache.tuscany.sca.runtime.RuntimeWire;
/**
* A resource collection binding listener, implemented as a Servlet and
* registered in a Servlet host provided by the SCA hosting runtime.
@ -71,6 +73,11 @@ class AtomBindingListenerServlet extends HttpServlet {
private static final Factory abderaFactory = Abdera.getNewFactory();
private static final Parser abderaParser = Abdera.getNewParser();
private static final String ETAG = "ETag";
private static final String LASTMODIFIED = "Last-Modified";
private static final String LOCATION = "Location";
private static final String CONTENTLOCATION = "Content-Location";
private static final SimpleDateFormat dateFormat = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss Z" ); // RFC 822 date time
private RuntimeWire wire;
private Invoker getFeedInvoker;
@ -248,18 +255,76 @@ class AtomBindingListenerServlet extends HttpServlet {
} else {
feed.setTitle("Feed");
}
// All feeds must provide Id and updated elements.
// However, some do not, so provide some program protection.
feed.setId( "Feed" + feed.hashCode());
Date lastModified = new Date( 0 );
// Add entries to the feed
for (Entry<Object, Object> entry: collection) {
org.apache.abdera.model.Entry feedEntry = feedEntry(entry, itemClassType, itemXMLType, mediator, abderaFactory);
// Use the most recent entry update as the feed update
Date entryUpdated = feedEntry.getUpdated();
if (( entryUpdated != null ) && (entryUpdated.compareTo( lastModified ) > 0 ))
lastModified = entryUpdated;
feed.addEntry(feedEntry);
}
// If no entries were newly updated,
if ( lastModified.compareTo( new Date( 0 ) ) == 0 )
lastModified = new Date();
}
}
if (feed != null) {
String feedETag = "\"" + generateFeedETag( feed ) + "\"";
Date feedUpdated = feed.getUpdated();
// Test request for predicates.
String predicate = request.getHeader( "If-Match" );
if (( predicate != null ) && ( !predicate.equals(feedETag) )) {
// No match, should short circuit
response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
return;
}
predicate = request.getHeader( "If-None-Match" );
if (( predicate != null ) && ( predicate.equals(feedETag) )) {
// Match, should short circuit
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
if ( feedUpdated != null ) {
predicate = request.getHeader( "If-Unmodified-Since" );
if ( predicate != null ) {
try {
Date predicateDate = dateFormat.parse( predicate );
if ( predicateDate.compareTo( feedUpdated ) < 0 ) {
// Match, should short circuit
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
} catch ( java.text.ParseException e ) {
// Ignore and move on
}
}
predicate = request.getHeader( "If-Modified-Since" );
if ( predicate != null ) {
try {
Date predicateDate = dateFormat.parse( predicate );
if ( predicateDate.compareTo( feedUpdated ) > 0 ) {
// Match, should short circuit
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
} catch ( java.text.ParseException e ) {
// Ignore and move on
}
}
}
// Write the Atom feed
response.setContentType("application/atom+xml; charset=utf-8");
// Provide Etag based on Id and time.
response.addHeader(ETAG, feedETag );
if ( feedUpdated != null )
response.addHeader(LASTMODIFIED, dateFormat.format( feedUpdated ));
try {
feed.getDocument().writeTo(response.getOutputStream());
} catch (IOException ioe) {
@ -270,7 +335,6 @@ class AtomBindingListenerServlet extends HttpServlet {
}
} else if (path.startsWith("/")) {
// Return a specific entry in the collection
org.apache.abdera.model.Entry feedEntry;
@ -282,7 +346,6 @@ class AtomBindingListenerServlet extends HttpServlet {
if (responseMessage.isFault()) {
throw new ServletException((Throwable)responseMessage.getBody());
}
if (supportsFeedEntries) {
// The service implementation returns a feed entry
feedEntry = responseMessage.getBody();
@ -292,10 +355,26 @@ class AtomBindingListenerServlet extends HttpServlet {
Entry<Object, Object> entry = new Entry<Object, Object>(id, responseMessage.getBody());
feedEntry = feedEntry(entry, itemClassType, itemXMLType, mediator, abderaFactory);
}
// Write the Atom entry
if (feedEntry != null) {
response.setContentType("application/atom+xml; charset=utf-8");
IRI feedId = feedEntry.getId();
if ( feedId != null )
response.addHeader(ETAG, "\"" + feedId.toString() + "\"" );
Date entryUpdated = feedEntry.getUpdated();
if ( entryUpdated != null )
response.addHeader(LASTMODIFIED, dateFormat.format( entryUpdated ));
// TODO Check If-Modified-Since If-Unmodified-Since predicates against LASTMODIFIED.
// If true return 304 and null body.
Link link = feedEntry.getSelfLink();
if (link != null) {
response.addHeader(LOCATION, link.getHref().toString());
} else {
link = feedEntry.getLink( "Edit" );
if (link != null) {
response.addHeader(LOCATION, link.getHref().toString());
}
}
response.setContentType("application/atom+xml;type=entry");
try {
feedEntry.writeTo(getWriter(response));
} catch (IOException ioe) {
@ -314,7 +393,6 @@ class AtomBindingListenerServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// Authenticate the user
String user = processAuthorizationHeader(request);
if (user == null) {
@ -390,14 +468,25 @@ class AtomBindingListenerServlet extends HttpServlet {
if (createdFeedEntry != null) {
// Set location of the created entry in the Location header
IRI feedId = createdFeedEntry.getId();
if ( feedId != null )
response.addHeader(ETAG, "\"" + feedId.toString() + "\"" );
Date entryUpdated = createdFeedEntry.getUpdated();
if ( entryUpdated != null )
response.addHeader(LASTMODIFIED, dateFormat.format( entryUpdated ));
Link link = createdFeedEntry.getSelfLink();
if (link != null) {
response.addHeader("Location", link.getHref().toString());
response.addHeader(LOCATION, link.getHref().toString());
} else {
link = createdFeedEntry.getLink( "Edit" );
if (link != null) {
response.addHeader(LOCATION, link.getHref().toString());
}
}
// Write the created Atom entry
response.setStatus(HttpServletResponse.SC_CREATED);
response.setContentType("application/atom+xml; charset=utf-8");
response.setContentType("application/atom+xml;type=entry");
try {
createdFeedEntry.writeTo(getWriter(response));
} catch (ParseException pe) {
@ -420,7 +509,6 @@ class AtomBindingListenerServlet extends HttpServlet {
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Authenticate the user
String user = processAuthorizationHeader(request);
if (user == null) {
@ -449,7 +537,6 @@ class AtomBindingListenerServlet extends HttpServlet {
// Let the component implementation create it
if (supportsFeedEntries) {
// The service implementation supports feed entries, pass the entry to it
Message requestMessage = messageFactory.createMessage();
requestMessage.setBody(new Object[] {id, feedEntry});
@ -463,7 +550,6 @@ class AtomBindingListenerServlet extends HttpServlet {
}
}
} else {
// The service implementation does not support feed entries, pass the data item to it
Message requestMessage = messageFactory.createMessage();
Entry<Object, Object> entry = entry(feedEntry, itemClassType, itemXMLType, mediator);
@ -600,4 +686,28 @@ class AtomBindingListenerServlet extends HttpServlet {
response.setHeader("WWW-Authenticate", "BASIC realm=\"Tuscany\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
/**
* Generate ETag based on feed Id and updated fields.
* @param feed
* @return ETag
*/
public static String generateFeedETag( Feed feed ) {
if ( feed == null ) {
return null;
}
IRI feedIdIRI = feed.getId();
String feedId = "ID";
if ( feedIdIRI != null ) {
feedId = feedIdIRI.toString();
}
Date feedUpdated = feed.getUpdated();
if ( feedUpdated == null ) {
return feedId;
}
return feedId + "-" + feedUpdated.hashCode();
}
}

View file

@ -0,0 +1,96 @@
/*
* 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.tuscany.sca.binding.atom;
import java.io.IOException;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Base;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.protocol.client.ClientResponse;
import org.apache.abdera.protocol.client.RequestOptions;
import org.apache.abdera.writer.Writer;
import org.apache.abdera.writer.WriterFactory;
/**
* Utilities to help print and test various aspects of entity tag support.
*/
public class AtomTestCaseUtils {
public static void prettyPrint(Abdera abdera, Base doc) throws IOException {
WriterFactory factory = abdera.getWriterFactory();
Writer writer = factory.getWriter("prettyxml");
writer.writeTo(doc, System.out);
System.out.println();
}
public static void printRequestHeaders( String title, String indent, RequestOptions request ) {
System.out.println( title );
if ( request == null ) {
System.out.println( indent + " request is null");
return;
}
String [] headerNames = request.getHeaderNames();
for ( String headerName: headerNames) {
String header = request.getHeader(headerName);
System.out.println( indent + " header name,value=" + headerName + "," + header );
}
}
public static void printResponseHeaders( String title, String indent, ClientResponse response ) {
System.out.println( title );
if ( response == null ) {
System.out.println( indent + " response is null");
return;
}
String [] headerNames = response.getHeaderNames();
for ( String headerName: headerNames) {
String header = response.getHeader(headerName);
System.out.println( indent + " header name,value=" + headerName + "," + header );
}
}
public static Entry newEntry(String value) {
Abdera abdera = new Abdera();
Entry entry = abdera.newEntry();
entry.setTitle("customer " + value);
Content content = abdera.getFactory().newContent();
content.setContentType(Content.Type.TEXT);
content.setValue(value);
entry.setContentElement(content);
return entry;
}
public static Entry updateEntry(Entry entry, String value) {
Abdera abdera = new Abdera();
entry.setTitle("customer " + value);
Content content = abdera.getFactory().newContent();
content.setContentType(Content.Type.TEXT);
content.setValue(value);
entry.setContentElement(content);
return entry;
}
}

View file

@ -0,0 +1,436 @@
/*
* 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.tuscany.sca.binding.atom;
import java.text.SimpleDateFormat;
import java.util.Date;
import junit.framework.Assert;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.tuscany.sca.binding.atom.collection.Collection;
import org.apache.tuscany.sca.host.embedded.SCADomain;
import org.apache.abdera.Abdera;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.model.Base;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Service;
import org.apache.abdera.protocol.Response.ResponseType;
import org.apache.abdera.protocol.client.AbderaClient;
import org.apache.abdera.protocol.client.ClientResponse;
import org.apache.abdera.protocol.client.RequestOptions;
import org.apache.abdera.protocol.client.util.BaseRequestEntity;
import org.apache.abdera.util.EntityTag;
import org.apache.abdera.parser.Parser;
/**
* Tests use of server provided entry entity tags for Atom binding in Tuscany.
* Tests conditional gets (e.g. get if-none-match) or conditional posts (post if-match)
* using entity tags or last modified header entries.
* Uses the SCA provided Provider composite to act as a server.
* Uses the Abdera provided Client to act as a client.
*/
public class ProviderEntryEntityTagsTest {
public final static String providerURI = "http://localhost:8084/customer";
protected static SCADomain scaConsumerDomain;
protected static SCADomain scaProviderDomain;
protected static CustomerClient testService;
protected static Abdera abdera;
protected static AbderaClient client;
protected static Parser abderaParser;
protected static String eTag;
protected static Date lastModified;
protected static final SimpleDateFormat dateFormat = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss Z" ); // RFC 822 date time
@BeforeClass
public static void init() throws Exception {
System.out.println(">>>ProviderEntryEntityTagsTest.init");
scaProviderDomain = SCADomain.newInstance("org/apache/tuscany/sca/binding/atom/Provider.composite");
abdera = new Abdera();
client = new AbderaClient(abdera);
abderaParser = Abdera.getNewParser();
}
@AfterClass
public static void destroy() throws Exception {
System.out.println(">>>ProviderEntryEntityTagsTest.destroy");
scaProviderDomain.close();
}
@Test
public void testPrelim() throws Exception {
Assert.assertNotNull(scaProviderDomain);
Assert.assertNotNull( client );
}
@Test
public void testEmptyCachePost() throws Exception {
// Pseudo-code
// 1) Example HTTP POST request (new entry put, new etag response)
// User client post request
// POST /myblog/entries HTTP/1.1
// Slug: First Post
//
// <?xml version="1.0" ?>
// <entry xmlns="http://www.w3.org/2005/Atom">
// <title>Atom-Powered Robots Run Amok</title>
// <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
// <updated>2007-02-123T17:09:02Z</updated>
// <author><name>Captain Lansing</name></author>
// <content>It's something moving... solid metal</content>
// </entry>
// Expected Atom server response (note unique ETag)
// HTTP/1.1 201 Created
// Date: Fri, 23 Feb 2007 21:17:11 GMT
// Content-Length: nnn
// Content-Type: application/atom+xml;type=entry
// Location: http://example.org/edit/first-post.atom
// Content-Location: http://example.org/edit/first-post.atom
// ETag: "e180ee84f0671b1"
// Last-Modified: Fri, 25 Jul 2008 14:36:44 -0500
//
// <?xml version="1.0" ?>
// <entry xmlns="http://www.w3.org/2005/Atom">
// <title>Atom-Powered Robots Run Amok</title>
// <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
// <updated>2007-02-123T17:09:02Z</updated>
// <author><name>Captain Lansing</name></author>
// <content>It's something moving... solid metal</content>
// </entry>
// Testing of entry creation
Factory factory = abdera.getFactory();
String customerName = "Fred Farkle";
Entry entry = factory.newEntry();
entry.setTitle("customer " + customerName);
entry.setUpdated(new Date());
entry.addAuthor("Apache Tuscany");
// ID created by collection.
// entry.setId(id); // auto-provided
// entry.addLink("" + id, "edit"); // auto-provided
// entry.addLink("" + id, "alternate"); // auto-provided
Content content = abdera.getFactory().newContent();
content.setContentType(Content.Type.TEXT);
content.setValue(customerName);
entry.setContentElement(content);
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
// AtomTestCaseUtils.printRequestHeaders( "Post request headers", " ", opts );
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
ClientResponse res = client.post(colUri.toString(), entry, opts);
// Assert response status code is 201-OK.
// Assert response header Content-Type: application/atom+xml; charset=UTF-8
// Assert response header Location: http://example.org/edit/first-post.atom
// Assert response header Content-Location: http://example.org/edit/first-post.atom
// Assert response header ETag: "e180ee84f0671b1"
// Assert response header Last-Modified: Fri, 25 Jul 2008 14:36:44 -0500
// Assert collection size is 1.
Assert.assertEquals(201, res.getStatus());
Assert.assertEquals(contentType, res.getContentType().toString().trim());
// Assert.assertNotNull( res.getLocation().toString() );
// Assert.assertEquals( "", res.getContentLocation().toString() );
// Save eTag for subsequent tests;
eTag = res.getHeader( "ETag" );
Assert.assertNotNull( eTag );
lastModified = res.getLastModified();
Assert.assertNotNull(lastModified);
}
@Test
public void testDirtyCachePut() throws Exception {
// 2) Conditional PUT request (post with etag. entry provided is stale)
// User client PUT request
// PUT /edit/first-post.atom HTTP/1.1
// > If-Match: "e180ee84f0671b1"
//
// <?xml version="1.0" ?>
// <entry xmlns="http://www.w3.org/2005/Atom">
// <title>Atom-Powered Robots Run Amok</title>
// <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
// <updated>2007-02-24T16:34:06Z</updated>
// <author><name>Captain Lansing</name></author>
// <content>Update: it's a hoax!</content>
// </entry>
// Testing of entry creation
Factory factory = abdera.getFactory();
String customerName = "Molly Ringwald";
Entry entry = factory.newEntry();
entry.setTitle("customer " + customerName);
entry.setUpdated( new Date());
entry.addAuthor("Apache Tuscany");
String id = eTag.substring( 1, eTag.length()-1);
entry.setId(id); // auto-provided
// entry.addLink("" + id, "edit"); // auto-provided
// entry.addLink("" + id, "alternate"); // auto-provided
Content content = abdera.getFactory().newContent();
content.setContentType(Content.Type.TEXT);
content.setValue(customerName);
entry.setContentElement(content);
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-None-Match", eTag);
AtomTestCaseUtils.printRequestHeaders( "Put request headers", " ", opts );
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.put(colUri.toString() + "/" + id, new BaseRequestEntity( entry ), opts);
// Expected Atom server response (item was edited by another user)
// > HTTP/1.1 412 Precondition Failed
// Date: Sat, 24 Feb 2007 16:34:11 GMT
// If-Match Assert response status code is 412. Precondition failed.
// If-None-Match Assert response status code is 200. OK
Assert.assertEquals(200, res.getStatus());
// Put provides null body and no etags.
res.release();
}
@Test
public void testETagMissGet() throws Exception {
// 4) Conditional GET example (get with etag. etag not in cache)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// > If-None-Match: "e180ee84f0671b1"
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-None-Match", "123456");
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
// Expected Atom server response (item was edited by another user)
// > HTTP/1.1 412 Precondition Failed
// Date: Sat, 24 Feb 2007 16:34:11 GMT
// Atom server response (item was up to date)
// > HTTP/1.1 200 OK
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// > ETag: "bb4f5e86e92ddb8549604a0df0763581"
// > Last-Modified: Mon, 28 Jul 2008 10:25:37 -0500
// Assert response status code is 200 OK.
// Assert header Content-Type: application/atom+xml;type=entry
// Assert header Location: http://example.org/edit/first-post.atom
// Assert header Content-Location: http://example.org/edit/first-post.atom
// Assert header ETag: "555555" (etag response != etag request)
// Assert header Last-Modified: Fri, 25 Jul 2008 14:36:44 -0500
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(contentType, res.getContentType().toString().trim());
// Assert.assertNotNull( res.getLocation().toString() );
// Assert.assertEquals( "", res.getContentLocation().toString() );
Assert.assertNotNull( res.getHeader( "ETag" ) );
lastModified = res.getLastModified();
Assert.assertNotNull(lastModified);
res.release();
}
@Test
public void testETagHitGet() throws Exception {
// 3) Conditional GET example (get with etag. etag match)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// > If-None-Match: "e180ee84f0671b1"
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-None-Match", eTag);
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
// Atom server response (item was up to date)
// > HTTP/1.1 304 Not Modified
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// Assert response status code is 304 Not Modified.
// Assert header ETag: "e180ee84f0671b1"
// Assert header Last-Modified: Fri, 25 Jul 2008 14:36:44 -0500
// Assert.assertEquals(304, res.getStatus());
res.release();
}
@Test
public void testUpToDateGet() throws Exception {
// 3) Conditional GET example (get with If-Mod. entry is up to date)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// > If-Modified-Since: Sat, 29 Oct 2025 19:43:31 GMT
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-Modified-Since", "Sat, 29 Oct 2025 19:43:31 GMT"); // "EEE, dd MMM yyyy HH:mm:ss Z // RFC 822 Date
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
ClientResponse res = client.execute( "GET", colUri.toString(), (BaseRequestEntity)null, opts);
// Atom server response (item was up to date)
// > HTTP/1.1 304 Not Modified
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// Assert response status code is 304 Not Modified.
Assert.assertEquals(304, res.getStatus());
res.release();
}
@Test
public void testOutOfDateGet() throws Exception {
// 4) Conditional GET example (get with If-Mod. entry is not to date)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// > If-Modified-Since: Sat, 29 Oct 1844 19:43:31 GMT
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-Modified-Since", "Sat, 29 Oct 1844 19:43:31 GMT"); // "EEE, dd MMM yyyy HH:mm:ss Z // RFC 822 Date
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
// Atom server response (item was up to date)
// > HTTP/1.1 200 OK
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// > ETag: "bb4f5e86e92ddb8549604a0df0763581"
// > Last-Modified: Mon, 28 Jul 2008 10:25:37 -0500
// Assert response status code is 200 OK.
// Assert header ETag: "e180ee84f0671b1"
// Assert header Last-Modified: Greater than If-Mod
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(contentType, res.getContentType().toString().trim());
// Assert.assertNotNull( res.getLocation().toString() );
// Assert.assertEquals( "", res.getContentLocation().toString() );
Assert.assertNotNull( res.getHeader( "ETag" ) );
lastModified = res.getLastModified();
Assert.assertNotNull(lastModified);
res.release();
}
@Test
public void testUpToDateUnModGet() throws Exception {
// 3) Conditional GET example (get with If-Unmod. entry is up to date)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// > If-Unmodified-Since: Sat, 29 Oct 2025 19:43:31 GMT
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-Unmodified-Since", "Sat, 29 Oct 2050 19:43:31 GMT" );
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
// Atom server response (item was up to date)
// > HTTP/1.1 304 Not Modified
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// Assert response status code is 304 Not Modified.
// Assert.assertEquals(304, res.getStatus());
// TODO Update when If-Unmodified-Since enabled.
Assert.assertEquals(200, res.getStatus());
res.release();
}
@Test
public void testOutOfDateUnModGet() throws Exception {
// 4) Conditional GET example (get with If-Unmod. entry is not to date)
// User client GET request
// GET /edit/first-post.atom HTTP/1.1
// Host: example.org
// > If-Unmodified-Since: Sat, 29 Oct 1844 19:43:31 GMT
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
opts.setHeader( "If-Unmodified-Since", "Sat, 29 Oct 1844 19:43:31 GMT" );
opts.setHeader( "Pragma", "no-cache"); // turn off client caching
IRI colUri = new IRI(providerURI).resolve("customer");
// res = client.post(colUri.toString() + "?test=foo", entry, opts);
String id = eTag.substring( 1, eTag.length()-1);
// Warning. AbderaClient.put(String uri,Base base,RequestOptions options) caches on the client side.
// ClientResponse res = client.put(colUri.toString() + id, entry, opts);
ClientResponse res = client.get(colUri.toString() + "/" + id, opts);
// Atom server response (item was up to date)
// > HTTP/1.1 200 OK
// Date: Sat, 24 Feb 2007 13:17:11 GMT
// > ETag: "bb4f5e86e92ddb8549604a0df0763581"
// > Last-Modified: Mon, 28 Jul 2008 10:25:37 -0500
// Assert response status code is 200 OK.
// Assert header Content-Type: application/atom+xml;type=entry
// Assert header Location: http://example.org/edit/first-post.atom
// Assert header Content-Location: http://example.org/edit/first-post.atom
// Assert header ETag: "e180ee84f0671b1"
// Assert header Last-Modified: Less than If-Unmod
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(contentType, res.getContentType().toString().trim());
// Assert.assertNotNull( res.getLocation().toString() );
// Assert.assertEquals( "", res.getContentLocation().toString() );
Assert.assertNotNull( res.getHeader( "ETag" ) );
lastModified = res.getLastModified();
Assert.assertNotNull(lastModified);
res.release();
}
}

View file

@ -0,0 +1,356 @@
/*
* 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.tuscany.sca.binding.atom;
import java.text.SimpleDateFormat;
import java.util.Date;
import junit.framework.Assert;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.tuscany.sca.host.embedded.SCADomain;
import org.apache.abdera.Abdera;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.factory.Factory;
import org.apache.abdera.model.Base;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Service;
import org.apache.abdera.model.Collection;
import org.apache.abdera.protocol.Response.ResponseType;
import org.apache.abdera.protocol.client.AbderaClient;
import org.apache.abdera.protocol.client.ClientResponse;
import org.apache.abdera.protocol.client.RequestOptions;
import org.apache.abdera.protocol.client.util.BaseRequestEntity;
import org.apache.abdera.util.EntityTag;
import org.apache.abdera.parser.Parser;
/**
* Tests use of server provided feed entity tags for Atom binding in Tuscany.
* Tests conditional gets (e.g. get if-none-match) or conditional posts (post if-match)
* using entity tags and last modified entries in headers.
* Uses the SCA provided Provider composite to act as a server.
* Uses the Abdera provided Client to act as a client.
*/
public class ProviderFeedEntityTagsTest {
public final static String providerURI = "http://localhost:8084/customer";
protected static SCADomain scaConsumerDomain;
protected static SCADomain scaProviderDomain;
protected static CustomerClient testService;
protected static Abdera abdera;
protected static AbderaClient client;
protected static Parser abderaParser;
protected static String eTag;
protected static Date lastModified;
protected static final SimpleDateFormat dateFormat = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss Z" ); // RFC 822 date time
@BeforeClass
public static void init() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.init");
scaProviderDomain = SCADomain.newInstance("org/apache/tuscany/sca/binding/atom/Provider.composite");
abdera = new Abdera();
client = new AbderaClient(abdera);
abderaParser = Abdera.getNewParser();
}
@AfterClass
public static void destroy() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.destroy");
scaProviderDomain.close();
}
@Test
public void testPrelim() throws Exception {
Assert.assertNotNull(scaProviderDomain);
Assert.assertNotNull( client );
}
@Test
public void testFeedBasics() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedBasics");
// Normal feed request
ClientResponse res = client.get(providerURI);
Assert.assertNotNull(res);
try {
// Asser feed provided since no predicates
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(ResponseType.SUCCESS, res.getType());
// AtomTestCaseUtils.printResponseHeaders( "Feed response headers:", " ", res );
// System.out.println("Feed response content:");
// AtomTestCaseUtils.prettyPrint(abdera, res.getDocument());
// Perform other tests on feed.
Document<Feed> doc = res.getDocument();
Assert.assertNotNull( doc );
Feed feed = doc.getRoot();
Assert.assertNotNull( feed );
printFeed( "Feed values", " ", feed );
// RFC 4287 requires non-null id, title, updated elements
Assert.assertNotNull( feed.getId() );
Assert.assertNotNull( feed.getTitle() );
Assert.assertNotNull( feed.getUpdated() );
eTag = res.getHeader("ETag");
Assert.assertNotNull( eTag );
lastModified = res.getLastModified();
Assert.assertNotNull( lastModified );
} finally {
res.release();
}
}
@Test
public void testUnmodifiedGetIfMatch() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfMatch");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Match", eTag);
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
String thisETag = res.getHeader("ETag");
Assert.assertNotNull( thisETag );
Date thisLastModified = res.getLastModified();
Assert.assertNotNull( thisLastModified );
// Should return 200 - Feed provided since it matches etag.
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(ResponseType.SUCCESS, res.getType());
// AtomTestCaseUtils.printResponseHeaders( "Feed response headers:", " ", res );
// System.out.println("Feed response content:");
// AtomTestCaseUtils.prettyPrint(abdera, res.getDocument());
} finally {
res.release();
}
}
@Test
public void testUnmodifiedGetIfNoneMatch() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfNoneMatch");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-None-Match", eTag);
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 304 - Feed not provided since it matches ETag.
Assert.assertEquals(304, res.getStatus());
} finally {
res.release();
}
}
@Test
public void testUnmodifiedGetIfUnModified() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfUnModified");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Unmodified-Since", dateFormat.format( new Date() ));
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 304 - Feed not provided since feed is modified since.
Assert.assertEquals(304, res.getStatus());
} finally {
res.release();
}
}
@Test
public void testUnmodifiedGetIfModified() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfModified");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Modified-Since", dateFormat.format( new Date( 0 ) ));
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 200 - Feed provided since feed is changed.
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(ResponseType.SUCCESS, res.getType());
String thisETag = res.getHeader("ETag");
Assert.assertNotNull( thisETag );
Date thisLastModified = res.getLastModified();
Assert.assertNotNull( thisLastModified );
} finally {
res.release();
}
}
@Test
public void testModifiedGetIfNoneMatch() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedModifiedGetIfNoneMatch");
// Post some new content to the feed.
Factory factory = abdera.getFactory();
String customerName = "Fred Farkle";
Entry entry = factory.newEntry();
entry.setTitle("customer " + customerName);
entry.setUpdated(new Date());
entry.addAuthor("Apache Tuscany");
Content content = abdera.getFactory().newContent();
content.setContentType(Content.Type.TEXT);
content.setValue(customerName);
entry.setContentElement(content);
RequestOptions opts = new RequestOptions();
String contentType = "application/atom+xml; type=entry";
opts.setContentType(contentType);
IRI colUri = new IRI(providerURI).resolve("customer");
ClientResponse res = client.post(colUri.toString(), entry, opts);
// Feed request with predicates
opts = new RequestOptions();
contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-None-Match", eTag);
res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
String thisETag = res.getHeader("ETag");
Assert.assertNotNull( thisETag );
Date thisLastModified = res.getLastModified();
Assert.assertNotNull( thisLastModified );
// Should return 200 - value since feed is changed
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(ResponseType.SUCCESS, res.getType());
// AtomTestCaseUtils.printResponseHeaders( "Feed modified if-none-match response headers:", " ", res );
// System.out.println("Feed response content:");
// AtomTestCaseUtils.prettyPrint(abdera, res.getDocument());
} finally {
res.release();
}
}
@Test
public void testModifiedGetIfMatch() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedModifiedGetIfMatch");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Match", eTag);
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 412 - Precondition failed since feed changed.
Assert.assertEquals(412, res.getStatus());
// AtomTestCaseUtils.printResponseHeaders( "Feed response headers:", " ", res );
// System.out.println("Feed response content:");
// AtomTestCaseUtils.prettyPrint(abdera, res.getDocument());
} finally {
res.release();
}
}
@Test
public void testModifiedGetIfUnModified() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfUnModified");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Unmodified-Since", dateFormat.format( new Date() ));
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 304 - Feed not provided since feed is modified since.
Assert.assertEquals(304, res.getStatus());
} finally {
res.release();
}
}
@Test
public void testModifiedGetIfModified() throws Exception {
System.out.println(">>>ProviderFeedEntityTagsTest.testFeedUnmodifiedGetIfModified");
// Feed request with predicates
RequestOptions opts = new RequestOptions();
final String contentType = "application/atom+xml";
opts.setContentType(contentType);
opts.setHeader( "If-Modified-Since", dateFormat.format( lastModified ));
ClientResponse res = client.get(providerURI, opts);
Assert.assertNotNull(res);
try {
// Should return 200 - Feed provided since feed is changed.
Assert.assertEquals(200, res.getStatus());
Assert.assertEquals(ResponseType.SUCCESS, res.getType());
String thisETag = res.getHeader("ETag");
Assert.assertNotNull( thisETag );
Date thisLastModified = res.getLastModified();
Assert.assertNotNull( thisLastModified );
} finally {
res.release();
}
}
public static void printFeed( String title, String indent, Feed feed ) {
if ( feed == null ) {
System.out.println( title + " feed is null");
return;
}
System.out.println( title );
System.out.println( indent + "id=" + feed.getId() );
System.out.println( indent + "title=" + feed.getTitle() );
System.out.println( indent + "updated=" + feed.getUpdated() );
System.out.println( indent + "author=" + feed.getAuthor() );
Collection collection = feed.getCollection();
if ( collection == null ) {
System.out.println( indent + "collection=null" );
} else {
System.out.println( indent + "collection=" + collection );
}
// System.out.println( indent + "collection size=" + feed.getCollection() );
// for (Collection collection : workspace.getCollections()) {
// if (collection.getTitle().equals("customers")) {
// String expected = uri + "customers";
// String actual = collection.getResolvedHref().toString();
// assertEquals(expected, actual);
// }
// }
}
}