Try iso8601SecondsDateParse if iso8601DateParse fails.

S3 compatible blobStores sometimes return date in the format:
"2014-07-23T20:53:17+0000" instead of the more common
"2014-07-23T18:09:39.944Z". This caused jclouds to barf with an
IllegalArgumentException.

This commit tries to parse both the formats for S3. The exception
is thrown if both fail.

Added unit tests for the same.
This commit is contained in:
Shri Javadekar 2014-07-25 16:59:18 -07:00 committed by Andrew Gaul
parent 3b2f02b723
commit 8ce9f79fde
9 changed files with 99 additions and 5 deletions

View File

@ -52,7 +52,8 @@ public class CopyObjectHandler extends ParseSax.HandlerWithResult<ObjectMetadata
if (qName.equals("ETag")) { if (qName.equals("ETag")) {
this.currentETag = currentOrNull(currentText); this.currentETag = currentOrNull(currentText);
} else if (qName.equals("LastModified")) { } else if (qName.equals("LastModified")) {
this.currentLastModified = dateParser.iso8601DateParse(currentOrNull(currentText)); this.currentLastModified = dateParser
.iso8601DateParseWithOptionalTZ(currentOrNull(currentText));
} else if (qName.equals("CopyObjectResult")) { } else if (qName.equals("CopyObjectResult")) {
metadata = new CopyObjectResult(currentLastModified, currentETag); metadata = new CopyObjectResult(currentLastModified, currentETag);
} }

View File

@ -69,7 +69,8 @@ public class ListAllMyBucketsHandler extends ParseSax.HandlerWithResult<Set<Buck
} else if (qName.equals("Name")) { } else if (qName.equals("Name")) {
currentName = currentOrNull(currentText); currentName = currentOrNull(currentText);
} else if (qName.equals("CreationDate")) { } else if (qName.equals("CreationDate")) {
currentCreationDate = dateParser.iso8601DateParse(currentOrNull(currentText)); currentCreationDate = dateParser
.iso8601DateParseWithOptionalTZ(currentOrNull(currentText));
} }
currentText = new StringBuilder(); currentText = new StringBuilder();
} }

View File

@ -97,7 +97,8 @@ public class ListBucketHandler extends ParseSax.HandlerWithResult<ListBucketResp
builder.key(currentKey); builder.key(currentKey);
builder.uri(uriBuilder(getRequest().getEndpoint()).clearQuery().appendPath(currentKey).build()); builder.uri(uriBuilder(getRequest().getEndpoint()).clearQuery().appendPath(currentKey).build());
} else if (qName.equals("LastModified")) { } else if (qName.equals("LastModified")) {
builder.lastModified(dateParser.iso8601DateParse(currentOrNull(currentText))); builder.lastModified(dateParser
.iso8601DateParseWithOptionalTZ(currentOrNull(currentText)));
} else if (qName.equals("ETag")) { } else if (qName.equals("ETag")) {
String currentETag = currentOrNull(currentText); String currentETag = currentOrNull(currentText);
builder.eTag(currentETag); builder.eTag(currentETag);

View File

@ -25,6 +25,7 @@ import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.functions.BaseHandlerTest; import org.jclouds.http.functions.BaseHandlerTest;
import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.ObjectMetadata;
import org.jclouds.s3.domain.internal.CopyObjectResult; import org.jclouds.s3.domain.internal.CopyObjectResult;
import org.jclouds.util.Strings2;
import org.testng.annotations.BeforeTest; import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -39,6 +40,8 @@ public class CopyObjectHandlerTest extends BaseHandlerTest {
private DateService dateService; private DateService dateService;
private final String copyObjectXML = "<CopyObjectResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><LastModified>2014-07-23T20:53:17+0000</LastModified><ETag>\"92836a3ea45a6984d1b4d23a747d46bb\"</ETag></CopyObjectResult>";
@BeforeTest @BeforeTest
@Override @Override
protected void setUpInjector() { protected void setUpInjector() {
@ -59,4 +62,20 @@ public class CopyObjectHandlerTest extends BaseHandlerTest {
assertEquals(result, expected); assertEquals(result, expected);
} }
/**
* Verifies that the parser doesn't barf if the timestamp in the copy object
* xml has time zone designators.
*/
public void testTimeStampWithTZ() {
InputStream is = Strings2.toInputStream(copyObjectXML);
ObjectMetadata expected = new CopyObjectResult(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2014-07-23T20:53:17+0000"),
"\"92836a3ea45a6984d1b4d23a747d46bb\"");
ObjectMetadata result = factory.create(
injector.getInstance(CopyObjectHandler.class)).parse(is);
assertEquals(result, expected);
}
} }

View File

@ -33,6 +33,7 @@ import org.jclouds.s3.domain.CanonicalUser;
import org.jclouds.s3.domain.ListBucketResponse; import org.jclouds.s3.domain.ListBucketResponse;
import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.ObjectMetadata;
import org.jclouds.s3.domain.ObjectMetadataBuilder; import org.jclouds.s3.domain.ObjectMetadataBuilder;
import org.jclouds.s3.domain.internal.CopyObjectResult;
import org.jclouds.s3.domain.internal.ListBucketResponseImpl; import org.jclouds.s3.domain.internal.ListBucketResponseImpl;
import org.jclouds.util.Strings2; import org.jclouds.util.Strings2;
import org.testng.annotations.Test; import org.testng.annotations.Test;
@ -48,6 +49,7 @@ import com.google.common.collect.ImmutableList;
@Test(groups = "unit", testName = "ListBucketHandlerTest") @Test(groups = "unit", testName = "ListBucketHandlerTest")
public class ListBucketHandlerTest extends BaseHandlerTest { public class ListBucketHandlerTest extends BaseHandlerTest {
public static final String listBucketWithPrefixAppsSlash = "<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Name>adriancole.org.jclouds.s3.amazons3testdelimiter</Name><Prefix>apps/</Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><IsTruncated>false</IsTruncated><Contents><Key>apps/0</Key><LastModified>2009-05-07T18:27:08.000Z</LastModified><ETag>&quot;c82e6a0025c31c5de5947fda62ac51ab&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/1</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;944fab2c5a9a6bacf07db5e688310d7a&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/2</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;a227b8888045c8fd159fb495214000f0&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/3</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;c9caa76c3dec53e2a192608ce73eef03&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/4</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;1ce5d0dcc6154a647ea90c7bdf82a224&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/5</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;79433524d87462ee05708a8ef894ed55&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/6</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;dd00a060b28ddca8bc5a21a49e306f67&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/7</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;8cd06eca6e819a927b07a285d750b100&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/8</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;174495094d0633b92cbe46603eee6bad&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/9</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;cd8a19b26fea8a827276df0ad11c580d&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents></ListBucketResult>"; public static final String listBucketWithPrefixAppsSlash = "<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Name>adriancole.org.jclouds.s3.amazons3testdelimiter</Name><Prefix>apps/</Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><IsTruncated>false</IsTruncated><Contents><Key>apps/0</Key><LastModified>2009-05-07T18:27:08.000Z</LastModified><ETag>&quot;c82e6a0025c31c5de5947fda62ac51ab&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/1</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;944fab2c5a9a6bacf07db5e688310d7a&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/2</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;a227b8888045c8fd159fb495214000f0&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/3</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;c9caa76c3dec53e2a192608ce73eef03&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/4</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;1ce5d0dcc6154a647ea90c7bdf82a224&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/5</Key><LastModified>2009-05-07T18:27:09.000Z</LastModified><ETag>&quot;79433524d87462ee05708a8ef894ed55&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/6</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;dd00a060b28ddca8bc5a21a49e306f67&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/7</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;8cd06eca6e819a927b07a285d750b100&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/8</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;174495094d0633b92cbe46603eee6bad&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>apps/9</Key><LastModified>2009-05-07T18:27:10.000Z</LastModified><ETag>&quot;cd8a19b26fea8a827276df0ad11c580d&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents></ListBucketResult>";
public static final String listBucketWithTSTimeZone = "<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Name>adriancole.org.jclouds.s3.amazons3testdelimiter</Name><Prefix>apps/</Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><IsTruncated>false</IsTruncated><Contents><Key>apps/9</Key><LastModified>2014-07-23T20:53:17+0000</LastModified><ETag>&quot;cd8a19b26fea8a827276df0ad11c580d&quot;</ETag><Size>8</Size><Owner><ID>e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0</ID><DisplayName>ferncam</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents></ListBucketResult>";
public static final String listBucketWithSlashDelimiterAndCommonPrefixApps = "<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"> <Delimiter>/</Delimiter> <CommonPrefixes><Prefix>apps/</Prefix></CommonPrefixes></ListBucketResult>"; public static final String listBucketWithSlashDelimiterAndCommonPrefixApps = "<ListBucketResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"> <Delimiter>/</Delimiter> <CommonPrefixes><Prefix>apps/</Prefix></CommonPrefixes></ListBucketResult>";
private DateService dateService = new SimpleDateFormatDateService(); private DateService dateService = new SimpleDateFormatDateService();
@ -132,7 +134,23 @@ public class ListBucketHandlerTest extends BaseHandlerTest {
assertEquals(bucket.getPrefix(), "apps/"); assertEquals(bucket.getPrefix(), "apps/");
assertEquals(bucket.getMaxKeys(), 1000); assertEquals(bucket.getMaxKeys(), 1000);
assert bucket.getMarker() == null; assert bucket.getMarker() == null;
} }
/**
* Verifies that the parser doesn't barf if the timestamp returned in the
* list bucket response has time zone designators in it.
*/
@Test
public void testListMyBucketsWithTZ() {
ListBucketResponse bucket = createParser().parse(
Strings2.toInputStream(listBucketWithTSTimeZone));
ObjectMetadata expected = new CopyObjectResult(
new SimpleDateFormatDateService()
.iso8601SecondsDateParse("2014-07-23T20:53:17+0000"),
"\"92836a3ea45a6984d1b4d23a747d46bb\"");
// Verify that the date was parsed successfully.
ObjectMetadata metadata = bucket.iterator().next();
assertEquals(metadata.getLastModified(), expected.getLastModified());
}
} }

View File

@ -76,6 +76,21 @@ public interface DateService {
*/ */
Date iso8601SecondsDateParse(String toParse) throws IllegalArgumentException; Date iso8601SecondsDateParse(String toParse) throws IllegalArgumentException;
/**
* Parse a given date in either of two iso8601 formats:
* "yyyy-MM-dd'T'HH:mm:ssZ" or "yyyy-MM-dd'T'HH:mm:ss.SSSZ". The latter one
* has the timezone designator, e.g. 2014-07-23T20:53:17+0000. At least one
* S3 compatible blobstore uses both these formats when returning
* container/object metadata.
*
* @param toParse
* The string to parse.
* @return the Date object of the parsed string.
* @throws IllegalArgumentException
*/
Date iso8601DateParseWithOptionalTZ(String toParse)
throws IllegalArgumentException;
String rfc1123DateFormat(Date date); String rfc1123DateFormat(Date date);
String rfc1123DateFormat(); String rfc1123DateFormat();

View File

@ -18,6 +18,7 @@ package org.jclouds.date.internal;
import static org.jclouds.date.internal.DateUtils.findTZ; import static org.jclouds.date.internal.DateUtils.findTZ;
import static org.jclouds.date.internal.DateUtils.trimTZ; import static org.jclouds.date.internal.DateUtils.trimTZ;
import static org.jclouds.date.internal.DateUtils.trimToMillis; import static org.jclouds.date.internal.DateUtils.trimToMillis;
import static org.jclouds.util.SaxUtils.currentOrNull;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -152,7 +153,8 @@ public class SimpleDateFormatDateService implements DateService {
} }
@Override @Override
public final Date iso8601SecondsDateParse(String toParse) { public final Date iso8601SecondsDateParse(String toParse)
throws IllegalArgumentException {
if (toParse.length() < 10) if (toParse.length() < 10)
throw new IllegalArgumentException("incorrect date format " + toParse); throw new IllegalArgumentException("incorrect date format " + toParse);
String tz = findTZ(toParse); String tz = findTZ(toParse);
@ -204,4 +206,18 @@ public class SimpleDateFormatDateService implements DateService {
} }
} }
} }
@Override
public Date iso8601DateParseWithOptionalTZ(String toParse)
throws IllegalArgumentException {
try {
return iso8601DateParse(toParse);
} catch (IllegalArgumentException orig) {
try {
return iso8601SecondsDateParse(toParse);
} catch (IllegalArgumentException ie) {
throw orig;
}
}
}
} }

View File

@ -109,6 +109,15 @@ public class DateServiceTest extends PerformanceTest {
assertEquals(dsDate, testData[0].date); assertEquals(dsDate, testData[0].date);
} }
@Test
public void testIso8601OptionalTZDateParse() {
Date dsDate = dateService
.iso8601DateParseWithOptionalTZ(testData[0].iso8601SecondsDateString);
Date secondsDate = dateService
.iso8601SecondsDateParse(testData[0].iso8601SecondsDateString);
assertEquals(dsDate, secondsDate);
}
@Test @Test
public void testIso8601SecondsDateParse() { public void testIso8601SecondsDateParse() {
Date dsDate = dateService.iso8601SecondsDateParse(testData[0].iso8601SecondsDateString); Date dsDate = dateService.iso8601SecondsDateParse(testData[0].iso8601SecondsDateString);

View File

@ -146,4 +146,18 @@ public class JodaDateService implements DateService {
public final Date rfc1123DateParse(String toParse) { public final Date rfc1123DateParse(String toParse) {
return rfc1123DateFormat.parseDateTime(toParse).toDate(); return rfc1123DateFormat.parseDateTime(toParse).toDate();
} }
@Override
public Date iso8601DateParseWithOptionalTZ(String toParse)
throws IllegalArgumentException {
try {
return iso8601DateParse(toParse);
} catch (IllegalArgumentException orig) {
try {
return iso8601SecondsDateParse(toParse);
} catch (IllegalArgumentException ie) {
throw orig;
}
}
}
} }