Updated Branches:
  refs/heads/master 622aec556 -> c11614400

Handle quoted ETags for OpenStack objects to fix JCLOUDS-247

This avoids a 'java.io.IOException: Unrecognized character: "' when a quoted 
ETag is encountered (which happens when getting a multipart blob from Rackspace 
Cloud).


Project: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/commit/c1161440
Tree: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/tree/c1161440
Diff: http://git-wip-us.apache.org/repos/asf/incubator-jclouds/diff/c1161440

Branch: refs/heads/master
Commit: c11614400fb86f6ab085cdcc8e08f76bf302d3b2
Parents: 622aec5
Author: Francis Devereux <[email protected]>
Authored: Tue Aug 20 17:54:59 2013 +0100
Committer: Everett Toews <[email protected]>
Committed: Sat Aug 24 21:03:47 2013 -0500

----------------------------------------------------------------------
 .../functions/ResourceToObjectInfo.java         |  6 +--
 .../functions/ParseObjectInfoFromHeaders.java   |  7 +--
 .../openstack/swift/utils/ETagUtils.java        | 51 ++++++++++++++++++++
 .../ParseObjectInfoFromHeadersTest.java         | 20 ++++++--
 .../openstack/swift/utils/ETagUtilsTest.java    | 34 +++++++++++++
 5 files changed, 108 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/c1161440/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java
----------------------------------------------------------------------
diff --git 
a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java
 
b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java
index 56dad87..45d8eb4 100644
--- 
a/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java
+++ 
b/apis/swift/src/main/java/org/jclouds/openstack/swift/blobstore/functions/ResourceToObjectInfo.java
@@ -16,8 +16,6 @@
  */
 package org.jclouds.openstack.swift.blobstore.functions;
 
-import static com.google.common.io.BaseEncoding.base16;
-
 import java.util.Map.Entry;
 
 import javax.inject.Singleton;
@@ -29,6 +27,7 @@ import 
org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
 import 
org.jclouds.openstack.swift.domain.internal.MutableObjectInfoWithMetadataImpl;
 
 import com.google.common.base.Function;
+import org.jclouds.openstack.swift.utils.ETagUtils;
 
 /**
  * @author Adrian Cole
@@ -48,7 +47,7 @@ public class ResourceToObjectInfo implements 
Function<StorageMetadata, MutableOb
          to.setContentType("application/directory");
       }
       if (from.getETag() != null && to.getHash() == null)
-         to.setHash(base16().lowerCase().decode(from.getETag()));
+         to.setHash(ETagUtils.convertHexETagToByteArray(from.getETag()));
       to.setName(from.getName());
       to.setLastModified(from.getLastModified());
       if (from.getUserMetadata() != null) {
@@ -57,5 +56,4 @@ public class ResourceToObjectInfo implements 
Function<StorageMetadata, MutableOb
       }
       return to;
    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/c1161440/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
----------------------------------------------------------------------
diff --git 
a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
 
b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
index 8a73d39..bd082fe 100644
--- 
a/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
+++ 
b/apis/swift/src/main/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeaders.java
@@ -17,10 +17,10 @@
 package org.jclouds.openstack.swift.functions;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.io.BaseEncoding.base16;
 import static org.jclouds.http.HttpUtils.attemptToParseSizeAndRangeFromHeaders;
 
 import javax.inject.Inject;
+import javax.ws.rs.core.HttpHeaders;
 
 import org.jclouds.blobstore.domain.BlobMetadata;
 import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
@@ -28,6 +28,7 @@ import org.jclouds.http.HttpRequest;
 import org.jclouds.http.HttpResponse;
 import org.jclouds.openstack.swift.blobstore.functions.ResourceToObjectInfo;
 import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
+import org.jclouds.openstack.swift.utils.ETagUtils;
 import org.jclouds.rest.InvocationContext;
 import org.jclouds.rest.internal.GeneratedHttpRequest;
 
@@ -60,9 +61,9 @@ public class ParseObjectInfoFromHeaders implements 
Function<HttpResponse, Mutabl
       to.setBytes(attemptToParseSizeAndRangeFromHeaders(from));
       to.setContainer(container);
       to.setUri(base.getUri());
-      String eTagHeader = from.getFirstHeaderOrNull("Etag");
+      String eTagHeader = from.getFirstHeaderOrNull(HttpHeaders.ETAG);
       if (eTagHeader != null) {
-         to.setHash(base16().lowerCase().decode(eTagHeader));
+         to.setHash(ETagUtils.convertHexETagToByteArray(eTagHeader));
       }
       return to;
    }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/c1161440/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java
----------------------------------------------------------------------
diff --git 
a/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java 
b/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java
new file mode 100644
index 0000000..9ffeb44
--- /dev/null
+++ b/apis/swift/src/main/java/org/jclouds/openstack/swift/utils/ETagUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jclouds.openstack.swift.utils;
+
+import java.util.regex.Pattern;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import static com.google.common.io.BaseEncoding.base16;
+
+/**
+ * @author Francis Devereux
+ */
+public class ETagUtils {
+   private static final Pattern QUOTED_STRING = Pattern.compile("^\"(.*)\"$");
+
+   /**
+    * <p>Converts the ETag of an OpenStack object to a byte array.</p>
+    *
+    * <p>Not applicable to all ETags, only those of OpenStack objects. 
According
+    * to the <a 
href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.11";>HTTP 
spec</a>
+    * an eTag can be any string, but the
+    * <a 
href="http://docs.openstack.org/trunk/openstack-object-storage/admin/content/additional-notes-on-large-objects.html";>OpenStack
 Object Storage Administration Guide</a>
+    * says that the ETag of an OpenStack object will be an MD5 sum (and MD5 
sums
+    * are conventionally represented as hex strings). This method only accepts
+    * hex strings as input, not arbitrary strings.</p>
+    */
+   public static byte[] convertHexETagToByteArray(String hexETag) {
+      hexETag = unquote(hexETag);
+      return base16().lowerCase().decode(hexETag);
+   }
+
+   @VisibleForTesting
+   static String unquote(String eTag) {
+      return QUOTED_STRING.matcher(eTag).replaceAll("$1");
+   }
+}

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/c1161440/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java
----------------------------------------------------------------------
diff --git 
a/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java
 
b/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java
index 7e62800..8c30fdb 100644
--- 
a/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java
+++ 
b/apis/swift/src/test/java/org/jclouds/openstack/swift/functions/ParseObjectInfoFromHeadersTest.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.openstack.swift.functions;
 
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
 
 import org.jclouds.http.HttpResponse;
@@ -26,7 +27,7 @@ import org.testng.annotations.Test;
 import com.google.common.collect.ImmutableList;
 
 /**
- * Tests behavior of {@code ParseContainerListFromJsonResponse}
+ * Tests behavior of {@code ParseObjectInfoFromHeaders}
  * 
  * @author Adrian Cole
  */
@@ -34,17 +35,30 @@ import com.google.common.collect.ImmutableList;
 public class ParseObjectInfoFromHeadersTest extends BasePayloadTest {
 
    public void testEtagCaseIssue() {
+      assertETagCanBeParsed("feb1",
+                            new byte[] { (byte) 0xfe, (byte) 0xb1 }
+      );
+   }
+
+   public void testParseEtagWithQuotes() {
+      assertETagCanBeParsed("\"feb1\"",
+                            new byte[] { (byte) 0xfe, (byte) 0xb1 }
+      );
+   }
+
+   private void assertETagCanBeParsed(String etag, byte[] expectedHash) {
       ParseObjectInfoFromHeaders parser = 
i.getInstance(ParseObjectInfoFromHeaders.class);
-      
+
       parser.setContext(requestForArgs(ImmutableList.<Object> of("container", 
"key")));
 
       HttpResponse response = 
HttpResponse.builder().statusCode(200).message("ok").payload("")
                                           .addHeader("Last-Modified", "Fri, 12 
Jun 2007 13:40:18 GMT")
                                           .addHeader("Content-Length", "0")
-                                          .addHeader("Etag", "feb1").build();
+                                          .addHeader("Etag", etag).build();
 
       response.getPayload().getContentMetadata().setContentType("text/plain");
       MutableObjectInfoWithMetadata md = parser.apply(response);
       assertNotNull(md.getHash());
+      assertEquals(md.getHash(), expectedHash);
    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-jclouds/blob/c1161440/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java 
b/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java
new file mode 100644
index 0000000..e46d792
--- /dev/null
+++ 
b/apis/swift/src/test/java/org/jclouds/openstack/swift/utils/ETagUtilsTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.jclouds.openstack.swift.utils;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * @author Francis Devereux
+ */
+@Test(groups = "unit")
+public class ETagUtilsTest {
+   @Test
+   public void testNoExceptionUnquotingSingleDQuote() {
+      String singleDQuoteCharacter = "\"";
+      assertEquals(ETagUtils.unquote(singleDQuoteCharacter),
+                   singleDQuoteCharacter);
+   }
+}

Reply via email to