From 974ba012a876ea8e3763b6134f667bf914e11905 Mon Sep 17 00:00:00 2001 From: Shri Javadekar Date: Fri, 25 Jul 2014 16:59:18 -0700 Subject: [PATCH] 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. --- .../org/jclouds/s3/xml/CopyObjectHandler.java | 3 ++- .../s3/xml/ListAllMyBucketsHandler.java | 3 ++- .../org/jclouds/s3/xml/ListBucketHandler.java | 3 ++- .../jclouds/s3/xml/CopyObjectHandlerTest.java | 19 ++++++++++++++++++ .../jclouds/s3/xml/ListBucketHandlerTest.java | 20 ++++++++++++++++++- .../java/org/jclouds/date/DateService.java | 15 ++++++++++++++ .../internal/SimpleDateFormatDateService.java | 18 ++++++++++++++++- .../org/jclouds/date/DateServiceTest.java | 9 +++++++++ .../jclouds/date/joda/JodaDateService.java | 14 +++++++++++++ 9 files changed, 99 insertions(+), 5 deletions(-) diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/CopyObjectHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/CopyObjectHandler.java index f54049663a6..12fdb070a90 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/xml/CopyObjectHandler.java +++ b/apis/s3/src/main/java/org/jclouds/s3/xml/CopyObjectHandler.java @@ -51,7 +51,8 @@ public void endElement(String uri, String name, String qName) { if (qName.equals("ETag")) { this.currentETag = currentOrNull(currentText); } else if (qName.equals("LastModified")) { - this.currentLastModified = dateParser.iso8601DateParse(currentOrNull(currentText)); + this.currentLastModified = dateParser + .iso8601DateParseWithOptionalTZ(currentOrNull(currentText)); } else if (qName.equals("CopyObjectResult")) { metadata = new CopyObjectResult(currentLastModified, currentETag); } diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/ListAllMyBucketsHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/ListAllMyBucketsHandler.java index ad601ff243d..1f3ffb54aea 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/xml/ListAllMyBucketsHandler.java +++ b/apis/s3/src/main/java/org/jclouds/s3/xml/ListAllMyBucketsHandler.java @@ -68,7 +68,8 @@ public void endElement(String uri, String name, String qName) { } else if (qName.equals("Name")) { currentName = currentOrNull(currentText); } else if (qName.equals("CreationDate")) { - currentCreationDate = dateParser.iso8601DateParse(currentOrNull(currentText)); + currentCreationDate = dateParser + .iso8601DateParseWithOptionalTZ(currentOrNull(currentText)); } currentText = new StringBuilder(); } diff --git a/apis/s3/src/main/java/org/jclouds/s3/xml/ListBucketHandler.java b/apis/s3/src/main/java/org/jclouds/s3/xml/ListBucketHandler.java index de81ff909d6..a53de4be14f 100644 --- a/apis/s3/src/main/java/org/jclouds/s3/xml/ListBucketHandler.java +++ b/apis/s3/src/main/java/org/jclouds/s3/xml/ListBucketHandler.java @@ -96,7 +96,8 @@ public void endElement(String uri, String name, String qName) { builder.key(currentKey); builder.uri(uriBuilder(getRequest().getEndpoint()).clearQuery().appendPath(currentKey).build()); } else if (qName.equals("LastModified")) { - builder.lastModified(dateParser.iso8601DateParse(currentOrNull(currentText))); + builder.lastModified(dateParser + .iso8601DateParseWithOptionalTZ(currentOrNull(currentText))); } else if (qName.equals("ETag")) { String currentETag = currentOrNull(currentText); builder.eTag(currentETag); diff --git a/apis/s3/src/test/java/org/jclouds/s3/xml/CopyObjectHandlerTest.java b/apis/s3/src/test/java/org/jclouds/s3/xml/CopyObjectHandlerTest.java index 961d2cc4690..559e597cc97 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/xml/CopyObjectHandlerTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/xml/CopyObjectHandlerTest.java @@ -25,6 +25,7 @@ import org.jclouds.http.functions.BaseHandlerTest; import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.internal.CopyObjectResult; +import org.jclouds.util.Strings2; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; @@ -37,6 +38,8 @@ public class CopyObjectHandlerTest extends BaseHandlerTest { private DateService dateService; + private final String copyObjectXML = "2014-07-23T20:53:17+0000\"92836a3ea45a6984d1b4d23a747d46bb\""; + @BeforeTest @Override protected void setUpInjector() { @@ -57,4 +60,20 @@ public void testApplyInputStream() { 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); + } } diff --git a/apis/s3/src/test/java/org/jclouds/s3/xml/ListBucketHandlerTest.java b/apis/s3/src/test/java/org/jclouds/s3/xml/ListBucketHandlerTest.java index 3ff7ff3522d..1e887b0658e 100644 --- a/apis/s3/src/test/java/org/jclouds/s3/xml/ListBucketHandlerTest.java +++ b/apis/s3/src/test/java/org/jclouds/s3/xml/ListBucketHandlerTest.java @@ -33,6 +33,7 @@ import org.jclouds.s3.domain.ListBucketResponse; import org.jclouds.s3.domain.ObjectMetadata; import org.jclouds.s3.domain.ObjectMetadataBuilder; +import org.jclouds.s3.domain.internal.CopyObjectResult; import org.jclouds.s3.domain.internal.ListBucketResponseImpl; import org.jclouds.util.Strings2; import org.testng.annotations.Test; @@ -46,6 +47,7 @@ @Test(groups = "unit", testName = "ListBucketHandlerTest") public class ListBucketHandlerTest extends BaseHandlerTest { public static final String listBucketWithPrefixAppsSlash = "adriancole.org.jclouds.s3.amazons3testdelimiterapps/1000falseapps/02009-05-07T18:27:08.000Z"c82e6a0025c31c5de5947fda62ac51ab"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/12009-05-07T18:27:09.000Z"944fab2c5a9a6bacf07db5e688310d7a"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/22009-05-07T18:27:09.000Z"a227b8888045c8fd159fb495214000f0"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/32009-05-07T18:27:09.000Z"c9caa76c3dec53e2a192608ce73eef03"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/42009-05-07T18:27:09.000Z"1ce5d0dcc6154a647ea90c7bdf82a224"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/52009-05-07T18:27:09.000Z"79433524d87462ee05708a8ef894ed55"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/62009-05-07T18:27:10.000Z"dd00a060b28ddca8bc5a21a49e306f67"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/72009-05-07T18:27:10.000Z"8cd06eca6e819a927b07a285d750b100"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/82009-05-07T18:27:10.000Z"174495094d0633b92cbe46603eee6bad"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARDapps/92009-05-07T18:27:10.000Z"cd8a19b26fea8a827276df0ad11c580d"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARD"; + public static final String listBucketWithTSTimeZone = "adriancole.org.jclouds.s3.amazons3testdelimiterapps/1000falseapps/92014-07-23T20:53:17+0000"cd8a19b26fea8a827276df0ad11c580d"8e1a5f66a480ca99a4fdfe8e318c3020446c9989d7004e7778029fbcc5d990fa0ferncamSTANDARD"; public static final String listBucketWithSlashDelimiterAndCommonPrefixApps = " / apps/"; private DateService dateService = new SimpleDateFormatDateService(); @@ -130,7 +132,23 @@ public void testListMyBucketsWithPrefixAppsSlash() throws HttpException { assertEquals(bucket.getPrefix(), "apps/"); assertEquals(bucket.getMaxKeys(), 1000); 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()); + } } diff --git a/core/src/main/java/org/jclouds/date/DateService.java b/core/src/main/java/org/jclouds/date/DateService.java index 3897d85003f..408f1258a57 100644 --- a/core/src/main/java/org/jclouds/date/DateService.java +++ b/core/src/main/java/org/jclouds/date/DateService.java @@ -73,6 +73,21 @@ public interface DateService { */ 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(); diff --git a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java index 38e1aa658bf..d928c3275e1 100644 --- a/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java +++ b/core/src/main/java/org/jclouds/date/internal/SimpleDateFormatDateService.java @@ -18,6 +18,7 @@ import static org.jclouds.date.internal.DateUtils.findTZ; import static org.jclouds.date.internal.DateUtils.trimTZ; import static org.jclouds.date.internal.DateUtils.trimToMillis; +import static org.jclouds.util.SaxUtils.currentOrNull; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -149,7 +150,8 @@ public final Date iso8601DateParse(String toParse) { } @Override - public final Date iso8601SecondsDateParse(String toParse) { + public final Date iso8601SecondsDateParse(String toParse) + throws IllegalArgumentException { if (toParse.length() < 10) throw new IllegalArgumentException("incorrect date format " + toParse); String tz = findTZ(toParse); @@ -201,4 +203,18 @@ public final Date rfc1123DateParse(String toParse) throws IllegalArgumentExcepti } } } + + @Override + public Date iso8601DateParseWithOptionalTZ(String toParse) + throws IllegalArgumentException { + try { + return iso8601DateParse(toParse); + } catch (IllegalArgumentException orig) { + try { + return iso8601SecondsDateParse(toParse); + } catch (IllegalArgumentException ie) { + throw orig; + } + } + } } diff --git a/core/src/test/java/org/jclouds/date/DateServiceTest.java b/core/src/test/java/org/jclouds/date/DateServiceTest.java index 5a91608c43e..4fa37123d3a 100644 --- a/core/src/test/java/org/jclouds/date/DateServiceTest.java +++ b/core/src/test/java/org/jclouds/date/DateServiceTest.java @@ -106,6 +106,15 @@ public void testIso8601DateParseTz() { 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 public void testIso8601SecondsDateParse() { Date dsDate = dateService.iso8601SecondsDateParse(testData[0].iso8601SecondsDateString); diff --git a/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java b/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java index 0438de72826..074758ab97d 100644 --- a/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java +++ b/drivers/joda/src/main/java/org/jclouds/date/joda/JodaDateService.java @@ -141,4 +141,18 @@ public final String rfc1123DateFormat() { public final Date rfc1123DateParse(String toParse) { 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; + } + } + } }