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).
This commit is contained in:
Francis Devereux 2013-08-20 17:54:59 +01:00 committed by Andrew Gaul
parent 0d0d5a0a03
commit aee3c10a93
5 changed files with 108 additions and 10 deletions

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}