mirror of https://github.com/apache/jclouds.git
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:
parent
622aec5566
commit
c11614400f
|
@ -16,8 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.openstack.swift.blobstore.functions;
|
package org.jclouds.openstack.swift.blobstore.functions;
|
||||||
|
|
||||||
import static com.google.common.io.BaseEncoding.base16;
|
|
||||||
|
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
@ -29,6 +27,7 @@ import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
|
||||||
import org.jclouds.openstack.swift.domain.internal.MutableObjectInfoWithMetadataImpl;
|
import org.jclouds.openstack.swift.domain.internal.MutableObjectInfoWithMetadataImpl;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
|
import org.jclouds.openstack.swift.utils.ETagUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
|
@ -48,7 +47,7 @@ public class ResourceToObjectInfo implements Function<StorageMetadata, MutableOb
|
||||||
to.setContentType("application/directory");
|
to.setContentType("application/directory");
|
||||||
}
|
}
|
||||||
if (from.getETag() != null && to.getHash() == null)
|
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.setName(from.getName());
|
||||||
to.setLastModified(from.getLastModified());
|
to.setLastModified(from.getLastModified());
|
||||||
if (from.getUserMetadata() != null) {
|
if (from.getUserMetadata() != null) {
|
||||||
|
@ -57,5 +56,4 @@ public class ResourceToObjectInfo implements Function<StorageMetadata, MutableOb
|
||||||
}
|
}
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
package org.jclouds.openstack.swift.functions;
|
package org.jclouds.openstack.swift.functions;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.io.BaseEncoding.base16;
|
|
||||||
import static org.jclouds.http.HttpUtils.attemptToParseSizeAndRangeFromHeaders;
|
import static org.jclouds.http.HttpUtils.attemptToParseSizeAndRangeFromHeaders;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
import org.jclouds.blobstore.domain.BlobMetadata;
|
import org.jclouds.blobstore.domain.BlobMetadata;
|
||||||
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
import org.jclouds.blobstore.functions.ParseSystemAndUserMetadataFromHeaders;
|
||||||
|
@ -28,6 +28,7 @@ import org.jclouds.http.HttpRequest;
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
import org.jclouds.openstack.swift.blobstore.functions.ResourceToObjectInfo;
|
import org.jclouds.openstack.swift.blobstore.functions.ResourceToObjectInfo;
|
||||||
import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
|
import org.jclouds.openstack.swift.domain.MutableObjectInfoWithMetadata;
|
||||||
|
import org.jclouds.openstack.swift.utils.ETagUtils;
|
||||||
import org.jclouds.rest.InvocationContext;
|
import org.jclouds.rest.InvocationContext;
|
||||||
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
import org.jclouds.rest.internal.GeneratedHttpRequest;
|
||||||
|
|
||||||
|
@ -60,9 +61,9 @@ public class ParseObjectInfoFromHeaders implements Function<HttpResponse, Mutabl
|
||||||
to.setBytes(attemptToParseSizeAndRangeFromHeaders(from));
|
to.setBytes(attemptToParseSizeAndRangeFromHeaders(from));
|
||||||
to.setContainer(container);
|
to.setContainer(container);
|
||||||
to.setUri(base.getUri());
|
to.setUri(base.getUri());
|
||||||
String eTagHeader = from.getFirstHeaderOrNull("Etag");
|
String eTagHeader = from.getFirstHeaderOrNull(HttpHeaders.ETAG);
|
||||||
if (eTagHeader != null) {
|
if (eTagHeader != null) {
|
||||||
to.setHash(base16().lowerCase().decode(eTagHeader));
|
to.setHash(ETagUtils.convertHexETagToByteArray(eTagHeader));
|
||||||
}
|
}
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.openstack.swift.functions;
|
package org.jclouds.openstack.swift.functions;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
import static org.testng.Assert.assertNotNull;
|
import static org.testng.Assert.assertNotNull;
|
||||||
|
|
||||||
import org.jclouds.http.HttpResponse;
|
import org.jclouds.http.HttpResponse;
|
||||||
|
@ -26,7 +27,7 @@ import org.testng.annotations.Test;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests behavior of {@code ParseContainerListFromJsonResponse}
|
* Tests behavior of {@code ParseObjectInfoFromHeaders}
|
||||||
*
|
*
|
||||||
* @author Adrian Cole
|
* @author Adrian Cole
|
||||||
*/
|
*/
|
||||||
|
@ -34,17 +35,30 @@ import com.google.common.collect.ImmutableList;
|
||||||
public class ParseObjectInfoFromHeadersTest extends BasePayloadTest {
|
public class ParseObjectInfoFromHeadersTest extends BasePayloadTest {
|
||||||
|
|
||||||
public void testEtagCaseIssue() {
|
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);
|
ParseObjectInfoFromHeaders parser = i.getInstance(ParseObjectInfoFromHeaders.class);
|
||||||
|
|
||||||
parser.setContext(requestForArgs(ImmutableList.<Object> of("container", "key")));
|
parser.setContext(requestForArgs(ImmutableList.<Object> of("container", "key")));
|
||||||
|
|
||||||
HttpResponse response = HttpResponse.builder().statusCode(200).message("ok").payload("")
|
HttpResponse response = HttpResponse.builder().statusCode(200).message("ok").payload("")
|
||||||
.addHeader("Last-Modified", "Fri, 12 Jun 2007 13:40:18 GMT")
|
.addHeader("Last-Modified", "Fri, 12 Jun 2007 13:40:18 GMT")
|
||||||
.addHeader("Content-Length", "0")
|
.addHeader("Content-Length", "0")
|
||||||
.addHeader("Etag", "feb1").build();
|
.addHeader("Etag", etag).build();
|
||||||
|
|
||||||
response.getPayload().getContentMetadata().setContentType("text/plain");
|
response.getPayload().getContentMetadata().setContentType("text/plain");
|
||||||
MutableObjectInfoWithMetadata md = parser.apply(response);
|
MutableObjectInfoWithMetadata md = parser.apply(response);
|
||||||
assertNotNull(md.getHash());
|
assertNotNull(md.getHash());
|
||||||
|
assertEquals(md.getHash(), expectedHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue