http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/AzureStorageResponseException.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/AzureStorageResponseException.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/AzureStorageResponseException.java new file mode 100644 index 0000000..744de08 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/AzureStorageResponseException.java @@ -0,0 +1,72 @@ +/* + * 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.azure.storage; + +import org.jclouds.azure.storage.domain.AzureStorageError; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; + +/** + * Encapsulates an Error from Azure Storage Services. + * + * @see <a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingRESTError.html" /> + * @see AzureStorageError + * @see org.jclouds.aws.handlers.ParseAzureStorageErrorFromXmlContent + */ +public class AzureStorageResponseException extends HttpResponseException { + + private AzureStorageError error = new AzureStorageError(); + + public AzureStorageResponseException(HttpCommand command, HttpResponse response, AzureStorageError error) { + super(String.format("command %s failed with code %s, error: %s", command.toString(), response + .getStatusCode(), error.toString()), command, response); + this.setError(error); + + } + + public AzureStorageResponseException(HttpCommand command, HttpResponse response, AzureStorageError error, + Throwable cause) { + super(String.format("command %1$s failed with error: %2$s", command.toString(), error + .toString()), command, response, cause); + this.setError(error); + + } + + public AzureStorageResponseException(String message, HttpCommand command, HttpResponse response, + AzureStorageError error) { + super(message, command, response); + this.setError(error); + + } + + public AzureStorageResponseException(String message, HttpCommand command, HttpResponse response, + AzureStorageError error, Throwable cause) { + super(message, command, response, cause); + this.setError(error); + + } + + public void setError(AzureStorageError error) { + this.error = error; + } + + public AzureStorageError getError() { + return error; + } + +}
http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/AzureStorageError.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/AzureStorageError.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/AzureStorageError.java new file mode 100644 index 0000000..ec57979 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/AzureStorageError.java @@ -0,0 +1,114 @@ +/* + * 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.azure.storage.domain; + +import java.util.Map; + +import com.google.common.collect.Maps; + +/** + * When an Azure Storage request is in error, the client receives an error response. + * + * @see <a href="http://msdn.microsoft.com/en-us/library/dd573365.aspx" /> + */ +public class AzureStorageError { + private String code; + private String message; + private String requestId; + private Map<String, String> details = Maps.newHashMap(); + private String stringSigned; + private String signature; + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("AzureError"); + sb.append("{requestId='").append(requestId).append('\''); + if (code != null) + sb.append(", code='").append(code).append('\''); + if (message != null) + sb.append(", message='").append(message).append('\''); + if (stringSigned != null) + sb.append(", stringSigned='").append(stringSigned).append('\''); + if (getSignature() != null) + sb.append(", signature='").append(getSignature()).append('\''); + if (!details.isEmpty()) + sb.append(", context='").append(details.toString()).append('\'').append('}'); + return sb.toString(); + } + + public void setCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + /** + * If a request is consistently failing and you have verified that the request is properly + * formulated, you may use this value to report the error to Microsoft. In your report, include + * the value of x-ms-request-id, the approximate time that the request was made, the storage + * service against which the request was made, and the type of operation that the request + * attempted + */ + public String getRequestId() { + return requestId; + } + + public void setStringSigned(String stringSigned) { + this.stringSigned = stringSigned; + } + + /** + * @return what jclouds signed before sending the request. + */ + public String getStringSigned() { + return stringSigned; + } + + public void setDetails(Map<String, String> context) { + this.details = context; + } + + /** + * @return additional details surrounding the error. + */ + public Map<String, String> getDetails() { + return details; + } + + public void setSignature(String signature) { + this.signature = signature; + } + + public String getSignature() { + return signature; + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/BoundedSet.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/BoundedSet.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/BoundedSet.java new file mode 100644 index 0000000..8778ae6 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/BoundedSet.java @@ -0,0 +1,33 @@ +/* + * 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.azure.storage.domain; + +import java.net.URI; +import java.util.Set; + +public interface BoundedSet<T> extends Set<T> { + URI getUrl(); + + String getPrefix(); + + String getMarker(); + + int getMaxResults(); + + String getNextMarker(); + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/internal/BoundedHashSet.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/internal/BoundedHashSet.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/internal/BoundedHashSet.java new file mode 100644 index 0000000..b9767ec --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/domain/internal/BoundedHashSet.java @@ -0,0 +1,64 @@ +/* + * 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.azure.storage.domain.internal; + +import java.net.URI; +import java.util.HashSet; + +import org.jclouds.azure.storage.domain.BoundedSet; + +import com.google.common.collect.Iterables; + +public class BoundedHashSet<T> extends HashSet<T> implements BoundedSet<T> { + + protected final URI url; + protected final String prefix; + protected final String marker; + protected final Integer maxResults; + protected final String nextMarker; + + public BoundedHashSet(Iterable<T> contents, URI url, String prefix, String marker, + Integer maxResults, String nextMarker) { + Iterables.addAll(this, contents); + this.url = url; + this.prefix = prefix; + this.nextMarker = nextMarker; + this.maxResults = maxResults; + this.marker = marker; + } + + public String getPrefix() { + return prefix; + } + + public String getMarker() { + return marker; + } + + public int getMaxResults() { + return maxResults; + } + + public String getNextMarker() { + return nextMarker; + } + + public URI getUrl() { + return url; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java new file mode 100644 index 0000000..8e56390 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/filters/SharedKeyLiteAuthentication.java @@ -0,0 +1,207 @@ +/* + * 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.azure.storage.filters; + +import static com.google.common.io.BaseEncoding.base64; +import static com.google.common.io.ByteStreams.readBytes; +import static org.jclouds.crypto.Macs.asByteProcessor; +import static org.jclouds.util.Patterns.NEWLINE_PATTERN; +import static org.jclouds.util.Strings2.toInputStream; + +import java.util.Collection; +import java.util.Set; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.crypto.Crypto; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Credentials; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.HttpUtils; +import org.jclouds.http.internal.SignatureWire; +import org.jclouds.logging.Logger; +import org.jclouds.util.Strings2; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.common.base.Supplier; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.Multimaps; +import com.google.common.collect.Sets; +import com.google.common.io.ByteProcessor; +import com.google.common.net.HttpHeaders; + +/** + * Signs the Azure Storage request. + * + * @see <a href= "http://msdn.microsoft.com/en-us/library/dd179428.aspx" /> + */ +@Singleton +public class SharedKeyLiteAuthentication implements HttpRequestFilter { + private static final Collection<String> FIRST_HEADERS_TO_SIGN = ImmutableList.of(HttpHeaders.DATE); + + private final SignatureWire signatureWire; + private final Supplier<Credentials> creds; + private final Provider<String> timeStampProvider; + private final Crypto crypto; + private final HttpUtils utils; + + @Resource + @Named(Constants.LOGGER_SIGNATURE) + Logger signatureLog = Logger.NULL; + + @Inject + public SharedKeyLiteAuthentication(SignatureWire signatureWire, + @org.jclouds.location.Provider Supplier<Credentials> creds, @TimeStamp Provider<String> timeStampProvider, + Crypto crypto, HttpUtils utils) { + this.crypto = crypto; + this.utils = utils; + this.signatureWire = signatureWire; + this.creds = creds; + this.timeStampProvider = timeStampProvider; + } + + public HttpRequest filter(HttpRequest request) throws HttpException { + request = replaceDateHeader(request); + String signature = calculateSignature(createStringToSign(request)); + request = replaceAuthorizationHeader(request, signature); + utils.logRequest(signatureLog, request, "<<"); + return request; + } + + HttpRequest replaceAuthorizationHeader(HttpRequest request, String signature) { + return request.toBuilder() + .replaceHeader(HttpHeaders.AUTHORIZATION, "SharedKeyLite " + creds.get().identity + ":" + signature) + .build(); + } + + HttpRequest replaceDateHeader(HttpRequest request) { + Builder<String, String> builder = ImmutableMap.builder(); + String date = timeStampProvider.get(); + builder.put(HttpHeaders.DATE, date); + request = request.toBuilder().replaceHeaders(Multimaps.forMap(builder.build())).build(); + return request; + } + + public String createStringToSign(HttpRequest request) { + utils.logRequest(signatureLog, request, ">>"); + StringBuilder buffer = new StringBuilder(); + // re-sign the request + appendMethod(request, buffer); + appendPayloadMetadata(request, buffer); + appendHttpHeaders(request, buffer); + appendCanonicalizedHeaders(request, buffer); + appendCanonicalizedResource(request, buffer); + if (signatureWire.enabled()) + signatureWire.output(buffer.toString()); + return buffer.toString(); + } + + private void appendPayloadMetadata(HttpRequest request, StringBuilder buffer) { + buffer.append( + HttpUtils.nullToEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() + .getContentMD5())).append("\n"); + buffer.append( + Strings.nullToEmpty(request.getPayload() == null ? null : request.getPayload().getContentMetadata() + .getContentType())).append("\n"); + } + + private String calculateSignature(String toSign) throws HttpException { + String signature = signString(toSign); + if (signatureWire.enabled()) + signatureWire.input(Strings2.toInputStream(signature)); + return signature; + } + + public String signString(String toSign) { + try { + ByteProcessor<byte[]> hmacSHA256 = asByteProcessor(crypto.hmacSHA256(base64().decode(creds.get().credential))); + return base64().encode(readBytes(toInputStream(toSign), hmacSHA256)); + } catch (Exception e) { + throw new HttpException("error signing request", e); + } + } + + private void appendMethod(HttpRequest request, StringBuilder toSign) { + toSign.append(request.getMethod()).append("\n"); + } + + private void appendCanonicalizedHeaders(HttpRequest request, StringBuilder toSign) { + // TreeSet == Sort the headers alphabetically. + Set<String> headers = Sets.newTreeSet(request.getHeaders().keySet()); + for (String header : headers) { + if (header.startsWith("x-ms-")) { + toSign.append(header.toLowerCase()).append(":"); + for (String value : request.getHeaders().get(header)) { + toSign.append(NEWLINE_PATTERN.matcher(value).replaceAll("")).append(","); + } + toSign.deleteCharAt(toSign.lastIndexOf(",")); + toSign.append("\n"); + } + } + } + + private void appendHttpHeaders(HttpRequest request, StringBuilder toSign) { + for (String header : FIRST_HEADERS_TO_SIGN) + toSign.append(HttpUtils.nullToEmpty(request.getHeaders().get(header))).append("\n"); + } + + @VisibleForTesting + void appendCanonicalizedResource(HttpRequest request, StringBuilder toSign) { + // 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of + // the identity that owns the resource being accessed. + toSign.append("/").append(creds.get().identity); + appendUriPath(request, toSign); + } + + @VisibleForTesting + void appendUriPath(HttpRequest request, StringBuilder toSign) { + // 2. Append the resource's encoded URI path + toSign.append(request.getEndpoint().getRawPath()); + + // If the request URI addresses a component of the + // resource, append the appropriate query string. The query string should include the question + // mark and the comp parameter (for example, ?comp=metadata). No other parameters should be + // included on the query string. + if (request.getEndpoint().getQuery() != null) { + StringBuilder paramsToSign = new StringBuilder("?"); + + String[] params = request.getEndpoint().getQuery().split("&"); + for (String param : params) { + String[] paramNameAndValue = param.split("="); + + if ("comp".equals(paramNameAndValue[0])) { + paramsToSign.append(param); + } + } + + if (paramsToSign.length() > 1) { + toSign.append(paramsToSign); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/AzureStorageClientErrorRetryHandler.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/AzureStorageClientErrorRetryHandler.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/AzureStorageClientErrorRetryHandler.java new file mode 100644 index 0000000..fadc772 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/AzureStorageClientErrorRetryHandler.java @@ -0,0 +1,89 @@ +/* + * 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.azure.storage.handlers; + +import java.io.ByteArrayInputStream; + +import javax.annotation.Resource; +import javax.inject.Named; + +import org.jclouds.Constants; +import org.jclouds.azure.storage.domain.AzureStorageError; +import org.jclouds.azure.storage.util.AzureStorageUtils; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpRetryHandler; +import org.jclouds.http.HttpUtils; +import org.jclouds.http.handlers.BackoffLimitedRetryHandler; +import org.jclouds.logging.Logger; + +import com.google.inject.Inject; + +/** + * Handles Retryable responses with error codes in the 4xx range + */ +public class AzureStorageClientErrorRetryHandler implements HttpRetryHandler { + + @Inject(optional = true) + @Named(Constants.PROPERTY_MAX_RETRIES) + private int retryCountLimit = 5; + + private final AzureStorageUtils utils; + private final BackoffLimitedRetryHandler backoffHandler; + + @Resource + protected Logger logger = Logger.NULL; + + @Inject + public AzureStorageClientErrorRetryHandler(BackoffLimitedRetryHandler backoffHandler, + AzureStorageUtils utils) { + this.backoffHandler = backoffHandler; + this.utils = utils; + } + + public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) { + byte[] content = HttpUtils.closeClientButKeepContentStream(response); + command.incrementFailureCount(); + if (!command.isReplayable()) { + logger.warn("Cannot retry after server error, command is not replayable: %1$s", command); + return false; + } else if (command.getFailureCount() > retryCountLimit) { + logger.warn( + "Cannot retry after server error, command has exceeded retry limit %1$d: %2$s", + retryCountLimit, command); + return false; + } else if (response.getStatusCode() == 409) { + // Content can be null in the case of HEAD requests + if (content != null) { + try { + AzureStorageError error = utils.parseAzureStorageErrorFromContent(command, response, + new ByteArrayInputStream(content)); + if ("ContainerBeingDeleted".equals(error.getCode())) { + backoffHandler.imposeBackoffExponentialDelay(100L, 3, retryCountLimit, command + .getFailureCount(), command.toString()); + return true; + } + } catch (HttpException e) { + logger.warn(e, "error parsing response: %s", new String(content)); + } + } + } + return false; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/ParseAzureStorageErrorFromXmlContent.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/ParseAzureStorageErrorFromXmlContent.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/ParseAzureStorageErrorFromXmlContent.java new file mode 100644 index 0000000..ece8176 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/handlers/ParseAzureStorageErrorFromXmlContent.java @@ -0,0 +1,115 @@ +/* + * 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.azure.storage.handlers; + +import static org.jclouds.http.HttpUtils.releasePayload; + +import java.io.IOException; +import java.util.regex.Pattern; + +import javax.annotation.Resource; +import javax.inject.Inject; + +import org.jclouds.azure.storage.AzureStorageResponseException; +import org.jclouds.azure.storage.domain.AzureStorageError; +import org.jclouds.azure.storage.util.AzureStorageUtils; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpErrorHandler; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.HttpResponseException; +import org.jclouds.logging.Logger; +import org.jclouds.rest.AuthorizationException; +import org.jclouds.rest.ResourceNotFoundException; +import org.jclouds.util.Strings2; + +/** + * This will parse and set an appropriate exception on the command object. + * + * @see AzureStorageError + */ +public class ParseAzureStorageErrorFromXmlContent implements HttpErrorHandler { + @Resource + protected Logger logger = Logger.NULL; + + private final AzureStorageUtils utils; + + @Inject + public ParseAzureStorageErrorFromXmlContent(AzureStorageUtils utils) { + this.utils = utils; + } + + public static final Pattern CONTAINER_PATH = Pattern.compile("^[/]?([^/]+)$"); + public static final Pattern CONTAINER_KEY_PATH = Pattern.compile("^[/]?([^/]+)/(.*)$"); + + public void handleError(HttpCommand command, HttpResponse response) { + Exception exception = new HttpResponseException(command, response); + String message = null; + AzureStorageError error = null; + try { + if (response.getPayload() != null) { + String contentType = response.getPayload().getContentMetadata().getContentType(); + if (contentType != null && (contentType.indexOf("xml") != -1 || contentType.indexOf("unknown") != -1) + && !Long.valueOf(0).equals(response.getPayload().getContentMetadata().getContentLength())) { + try { + error = utils.parseAzureStorageErrorFromContent(command, response, response.getPayload().getInput()); + if (error != null) { + message = error.getMessage(); + exception = new AzureStorageResponseException(command, response, error); + } + } catch (RuntimeException e) { + try { + message = Strings2.toStringAndClose(response.getPayload().openStream()); + exception = new HttpResponseException(command, response, message); + } catch (IOException e1) { + } + } + } else { + try { + message = Strings2.toStringAndClose(response.getPayload().openStream()); + exception = new HttpResponseException(command, response, message); + } catch (IOException e) { + } + } + } + message = message != null ? message : String.format("%s -> %s", command.getCurrentRequest().getRequestLine(), + response.getStatusLine()); + exception = refineException(command, response, exception, error, message); + } finally { + releasePayload(response); + command.setException(exception); + } + } + + protected Exception refineException(HttpCommand command, HttpResponse response, Exception exception, + AzureStorageError error, String message) { + switch (response.getStatusCode()) { + case 401: + exception = new AuthorizationException(message, exception); + break; + case 404: + if (!command.getCurrentRequest().getMethod().equals("DELETE")) { + exception = new ResourceNotFoundException(message, exception); + } + break; + case 411: + exception = new IllegalArgumentException(message); + break; + } + return exception; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/CreateOptions.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/CreateOptions.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/CreateOptions.java new file mode 100644 index 0000000..20fd7f8 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/CreateOptions.java @@ -0,0 +1,74 @@ +/* + * 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.azure.storage.options; + +import java.util.Map.Entry; + +import org.jclouds.azure.storage.reference.AzureStorageHeaders; +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.collect.Multimap; + +/** + * Contains common options supported in the REST API for the Create operation. <h2> + * Usage</h2> The recommended way to instantiate a CreateOptions object is to statically import + * CreateOptions.* and invoke a static creation method followed by an instance mutator (if + * needed): + * <p/> + * <code> + * import static CreateOptions.Builder.* + * import org.jclouds.azure.storage.queue.AzureQueueClient; + * <p/> + * AzureQueueClient connection = // get connection + * Multimap<String,String> metadata = // ... + * boolean createdWithPublicAcl = connection.createQueue("containerName", withMetadata(metadata)); + * <code> * + * + * @see <a href="http://msdn.microsoft.com/en-us/library/dd179466.aspx" /> + */ +public class CreateOptions extends BaseHttpRequestOptions { + public static final CreateOptions NONE = new CreateOptions(); + + /** + * A name-value pair to associate with the container as metadata. + * + * Note that these are stored at the server under the prefix: x-ms-meta- + */ + public CreateOptions withMetadata(Multimap<String, String> metadata) { + for (Entry<String, String> entry : metadata.entries()) { + if (entry.getKey().startsWith(AzureStorageHeaders.USER_METADATA_PREFIX)) + headers.put(entry.getKey(), entry.getValue()); + else + headers + .put(AzureStorageHeaders.USER_METADATA_PREFIX + entry.getKey(), entry + .getValue()); + } + return this; + } + + public static class Builder { + + /** + * @see CreateOptions#withMetadata(Multimap<String, String>) + */ + public static CreateOptions withMetadata(Multimap<String, String> metadata) { + CreateOptions options = new CreateOptions(); + return options.withMetadata(metadata); + } + + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/ListOptions.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/ListOptions.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/ListOptions.java new file mode 100644 index 0000000..fba5f39 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/options/ListOptions.java @@ -0,0 +1,131 @@ +/* + * 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.azure.storage.options; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import org.jclouds.http.options.BaseHttpRequestOptions; + +import com.google.common.collect.ImmutableSet; + +/** + * Options used to control paginated results (aka list commands). + * + * @see <a href="http://msdn.microsoft.com/en-us/library/dd179466.aspx" /> + */ +public class ListOptions extends BaseHttpRequestOptions { + public static final ListOptions NONE = new ListOptions(); + + /** + * Include this parameter to specify that the container's metadata be returned as part of the + * response body. + * + * Note that metadata requested with this parameter must be stored in accordance with the naming + * restrictions imposed by the 2009-09-19 version of the Blob service. Beginning with this + * version, all metadata names must adhere to the naming conventions for C# identifiers. + */ + public ListOptions includeMetadata() { + this.queryParameters.replaceValues("include", ImmutableSet.of("metadata")); + return this; + } + + public boolean getIncludeMetadata() { + return getFirstQueryOrNull("include").equals("metadata"); + } + + /** + * Filters the results to return only objects whose name begins with the specified prefix. + */ + public ListOptions prefix(String prefix) { + this.queryParameters.put("prefix", checkNotNull(prefix, "prefix")); + return this; + } + + public String getPrefix() { + return getFirstQueryOrNull("prefix"); + } + + /** + * A string value that identifies the portion of the list to be returned with the next list + * operation. The operation returns a marker value within the response body if the list returned + * was not complete. The marker value may then be used in a subsequent call to request the next + * set of list items. + * <p/> + * The marker value is opaque to the client. + */ + public ListOptions marker(String marker) { + this.queryParameters.put("marker", checkNotNull(marker, "marker")); + return this; + } + + public String getMarker() { + return getFirstQueryOrNull("marker"); + } + + /** + * Specifies the maximum number of containers to return. If maxresults is not specified, the + * server will return up to 5,000 items. If the parameter is set to a value greater than 5,000, + * the server will return a Bad Request (400) error + */ + public ListOptions maxResults(int maxresults) { + checkState(maxresults >= 0, "maxresults must be >= 0"); + checkState(maxresults <= 10000, "maxresults must be <= 5000"); + queryParameters.put("maxresults", Integer.toString(maxresults)); + return this; + } + + public Integer getMaxResults() { + String maxresults = getFirstQueryOrNull("maxresults"); + return (maxresults != null) ? Integer.valueOf(maxresults) : null; + } + + public static class Builder { + /** + * @see ListOptions#includeMetadata() + */ + public static ListOptions includeMetadata() { + ListOptions options = new ListOptions(); + return options.includeMetadata(); + } + + /** + * @see ListOptions#prefix(String) + */ + public static ListOptions prefix(String prefix) { + ListOptions options = new ListOptions(); + return options.prefix(prefix); + } + + /** + * @see ListOptions#marker(String) + */ + public static ListOptions marker(String marker) { + ListOptions options = new ListOptions(); + return options.marker(marker); + } + + /** + * @see ListOptions#maxResults(long) + */ + public static ListOptions maxResults(int maxKeys) { + ListOptions options = new ListOptions(); + return options.maxResults(maxKeys); + } + + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java new file mode 100644 index 0000000..0c60e51 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/reference/AzureStorageHeaders.java @@ -0,0 +1,33 @@ +/* + * 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.azure.storage.reference; + +/** + * Additional headers specified by Azure Storage REST API. + * + * @see <a href="http://msdn.microsoft.com/en-us/library/dd179357.aspx" /> + */ +public final class AzureStorageHeaders { + + public static final String USER_METADATA_PREFIX = "x-ms-meta-"; + public static final String REQUEST_ID = "x-ms-request-id"; + public static final String VERSION = "x-ms-version"; + + private AzureStorageHeaders() { + throw new AssertionError("intentionally unimplemented"); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/AzureStorageUtils.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/AzureStorageUtils.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/AzureStorageUtils.java new file mode 100644 index 0000000..bf62ff82a --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/util/AzureStorageUtils.java @@ -0,0 +1,59 @@ +/* + * 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.azure.storage.util; + +import java.io.InputStream; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.jclouds.azure.storage.domain.AzureStorageError; +import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; +import org.jclouds.azure.storage.reference.AzureStorageHeaders; +import org.jclouds.azure.storage.xml.ErrorHandler; +import org.jclouds.http.HttpCommand; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpResponse; +import org.jclouds.http.functions.ParseSax; + +/** + * Encryption, Hashing, and IO Utilities needed to sign and verify Azure Storage requests and + * responses. + */ +public class AzureStorageUtils { + + @Inject + SharedKeyLiteAuthentication signer; + + @Inject + ParseSax.Factory factory; + + @Inject + Provider<ErrorHandler> errorHandlerProvider; + + public AzureStorageError parseAzureStorageErrorFromContent(HttpCommand command, + HttpResponse response, InputStream content) throws HttpException { + AzureStorageError error = factory.create(errorHandlerProvider.get()).parse(content); + error.setRequestId(response.getFirstHeaderOrNull(AzureStorageHeaders.REQUEST_ID)); + if ("AuthenticationFailed".equals(error.getCode())) { + error.setStringSigned(signer.createStringToSign(command.getCurrentRequest())); + error.setSignature(signer.signString(error.getStringSigned())); + } + return error; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azure/storage/xml/ErrorHandler.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azure/storage/xml/ErrorHandler.java b/providers/azureblob/src/main/java/org/jclouds/azure/storage/xml/ErrorHandler.java new file mode 100644 index 0000000..2467fe7 --- /dev/null +++ b/providers/azureblob/src/main/java/org/jclouds/azure/storage/xml/ErrorHandler.java @@ -0,0 +1,53 @@ +/* + * 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.azure.storage.xml; + +import org.jclouds.azure.storage.domain.AzureStorageError; +import org.jclouds.http.functions.ParseSax; + +/** + * Parses the error from the Amazon S3 REST API. + * + * @see <a + * href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?UsingRESTError.html" + * /> + */ +public class ErrorHandler extends ParseSax.HandlerWithResult<AzureStorageError> { + + private AzureStorageError error = new AzureStorageError(); + private StringBuilder currentText = new StringBuilder(); + + public AzureStorageError getResult() { + return error; + } + + public void endElement(String uri, String name, String qName) { + + if (qName.equals("Code")) { + error.setCode(currentText.toString().trim()); + } else if (qName.equals("Message")) { + error.setMessage(currentText.toString().trim()); + } else if (!qName.equals("Error")) { + error.getDetails().put(qName, currentText.toString()); + } + currentText.setLength(0); + } + + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java index 8c5744a..ade52bc 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobApiMetadata.java @@ -23,28 +23,15 @@ import java.net.URI; import java.util.Properties; import org.jclouds.azureblob.blobstore.config.AzureBlobStoreContextModule; -import org.jclouds.azureblob.config.AzureBlobRestClientModule; +import org.jclouds.azureblob.config.AzureBlobHttpApiModule; import org.jclouds.blobstore.BlobStoreContext; -import org.jclouds.rest.internal.BaseRestApiMetadata; +import org.jclouds.rest.internal.BaseHttpApiMetadata; import com.google.common.collect.ImmutableSet; -import com.google.common.reflect.TypeToken; import com.google.inject.Module; -/** - * Implementation of {@link ApiMetadata} for Microsoft Azure Blob Service API - */ -public class AzureBlobApiMetadata extends BaseRestApiMetadata { +public class AzureBlobApiMetadata extends BaseHttpApiMetadata { - /** - * @deprecated please use {@code org.jclouds.ContextBuilder#buildApi(AzureBlobClient.class)} as - * {@link AzureBlobAsyncClient} interface will be removed in jclouds 1.7. - */ - @Deprecated - public static final TypeToken<org.jclouds.rest.RestContext<AzureBlobClient, AzureBlobAsyncClient>> CONTEXT_TOKEN = new TypeToken<org.jclouds.rest.RestContext<AzureBlobClient, AzureBlobAsyncClient>>() { - private static final long serialVersionUID = 1L; - }; - private static Builder builder() { return new Builder(); } @@ -63,15 +50,14 @@ public class AzureBlobApiMetadata extends BaseRestApiMetadata { } public static Properties defaultProperties() { - Properties properties = BaseRestApiMetadata.defaultProperties(); + Properties properties = BaseHttpApiMetadata.defaultProperties(); properties.setProperty(PROPERTY_USER_METADATA_PREFIX, "x-ms-meta-"); return properties; } - public static class Builder extends BaseRestApiMetadata.Builder<Builder> { - @SuppressWarnings("deprecation") + public static class Builder extends BaseHttpApiMetadata.Builder<AzureBlobClient, Builder> { protected Builder() { - super(AzureBlobClient.class, AzureBlobAsyncClient.class); + super(AzureBlobClient.class); id("azureblob") .name("Microsoft Azure Blob Service API") .identityName("Account Name") @@ -81,7 +67,7 @@ public class AzureBlobApiMetadata extends BaseRestApiMetadata { .documentation(URI.create("http://msdn.microsoft.com/en-us/library/dd135733.aspx")) .defaultProperties(AzureBlobApiMetadata.defaultProperties()) .view(typeToken(BlobStoreContext.class)) - .defaultModules(ImmutableSet.<Class<? extends Module>>of(AzureBlobRestClientModule.class, AzureBlobStoreContextModule.class)); + .defaultModules(ImmutableSet.<Class<? extends Module>>of(AzureBlobHttpApiModule.class, AzureBlobStoreContextModule.class)); } @Override http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobAsyncClient.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobAsyncClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobAsyncClient.java deleted file mode 100644 index ea44e96..0000000 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobAsyncClient.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * 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.azureblob; - -import static com.google.common.net.HttpHeaders.EXPECT; - -import java.util.List; -import java.util.Map; - -import javax.inject.Named; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.HEAD; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.QueryParam; - -import org.jclouds.Fallbacks.TrueOnNotFoundOr404; -import org.jclouds.Fallbacks.VoidOnNotFoundOr404; -import org.jclouds.azure.storage.domain.BoundedSet; -import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; -import org.jclouds.azure.storage.options.ListOptions; -import org.jclouds.azure.storage.reference.AzureStorageHeaders; -import org.jclouds.azureblob.AzureBlobFallbacks.FalseIfContainerAlreadyExists; -import org.jclouds.azureblob.binders.BindAzureBlobMetadataToRequest; -import org.jclouds.azureblob.binders.BindAzureBlocksToRequest; -import org.jclouds.azureblob.domain.BlobProperties; -import org.jclouds.azureblob.domain.ContainerProperties; -import org.jclouds.azureblob.domain.ListBlobBlocksResponse; -import org.jclouds.azureblob.domain.ListBlobsResponse; -import org.jclouds.azureblob.domain.PublicAccess; -import org.jclouds.azureblob.functions.BlobName; -import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent; -import org.jclouds.azureblob.functions.ParseBlobPropertiesFromHeaders; -import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders; -import org.jclouds.azureblob.functions.ParsePublicAccessHeader; -import org.jclouds.azureblob.options.CreateContainerOptions; -import org.jclouds.azureblob.options.ListBlobsOptions; -import org.jclouds.azureblob.predicates.validators.BlockIdValidator; -import org.jclouds.azureblob.predicates.validators.ContainerNameValidator; -import org.jclouds.azureblob.xml.AccountNameEnumerationResultsHandler; -import org.jclouds.azureblob.xml.BlobBlocksResultsHandler; -import org.jclouds.azureblob.xml.ContainerNameEnumerationResultsHandler; -import org.jclouds.blobstore.BlobStoreFallbacks.FalseOnContainerNotFound; -import org.jclouds.blobstore.BlobStoreFallbacks.FalseOnKeyNotFound; -import org.jclouds.blobstore.BlobStoreFallbacks.NullOnContainerNotFound; -import org.jclouds.blobstore.BlobStoreFallbacks.NullOnKeyNotFound; -import org.jclouds.blobstore.binders.BindMapToHeadersWithPrefix; -import org.jclouds.http.functions.ParseETagHeader; -import org.jclouds.http.options.GetOptions; -import org.jclouds.io.Payload; -import org.jclouds.rest.annotations.BinderParam; -import org.jclouds.rest.annotations.Fallback; -import org.jclouds.rest.annotations.Headers; -import org.jclouds.rest.annotations.ParamParser; -import org.jclouds.rest.annotations.ParamValidators; -import org.jclouds.rest.annotations.QueryParams; -import org.jclouds.rest.annotations.RequestFilters; -import org.jclouds.rest.annotations.ResponseParser; -import org.jclouds.rest.annotations.SkipEncoding; -import org.jclouds.rest.annotations.XMLResponseParser; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.inject.Provides; - -/** - * Provides asynchronous access to Azure Blob via their REST API. - * <p/> - * All commands return a ListenableFuture of the result from Azure Blob. Any exceptions incurred - * during processing will be backend in an {@link ExecutionException} as documented in - * {@link ListenableFuture#get()}. - * - * @see <a href="http://msdn.microsoft.com/en-us/library/dd135733.aspx" /> - * @see AzureBlobClient - * @deprecated please use {@code org.jclouds.ContextBuilder#buildApi(AzureBlobClient.class)} as - * {@link AzureBlobAsyncClient} interface will be removed in jclouds 1.7. - */ -@Deprecated -@RequestFilters(SharedKeyLiteAuthentication.class) -@Headers(keys = AzureStorageHeaders.VERSION, values = "2012-02-12") -@SkipEncoding({ '/', '$' }) -@Path("/") -public interface AzureBlobAsyncClient { - @Provides - org.jclouds.azureblob.domain.AzureBlob newBlob(); - - /** - * @see AzureBlobClient#listContainers - */ - @Named("ListContainers") - @GET - @XMLResponseParser(AccountNameEnumerationResultsHandler.class) - @QueryParams(keys = "comp", values = "list") - ListenableFuture<? extends BoundedSet<ContainerProperties>> listContainers(ListOptions... listOptions); - - /** - * @see AzureBlobClient#createContainer - */ - @Named("CreateContainer") - @PUT - @Path("{container}") - @Fallback(FalseIfContainerAlreadyExists.class) - @QueryParams(keys = "restype", values = "container") - ListenableFuture<Boolean> createContainer( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - CreateContainerOptions... options); - - /** - * @see AzureBlobClient#getPublicAccessForContainer - */ - @Named("GetContainerACL") - @HEAD - @Path("{container}") - @QueryParams(keys = { "restype", "comp" }, values = { "container", "acl" }) - @ResponseParser(ParsePublicAccessHeader.class) - @Fallback(NullOnContainerNotFound.class) - ListenableFuture<PublicAccess> getPublicAccessForContainer( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); - - /** - * @see AzureBlobClient#getContainerProperties - */ - @Named("GetContainerProperties") - @HEAD - @Path("{container}") - @QueryParams(keys = "restype", values = "container") - @ResponseParser(ParseContainerPropertiesFromHeaders.class) - @Fallback(NullOnContainerNotFound.class) - ListenableFuture<ContainerProperties> getContainerProperties( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); - - /** - * @see AzureBlobClient#containerExists - */ - @Named("GetContainerProperties") - @HEAD - @Path("{container}") - @QueryParams(keys = "restype", values = "container") - @Fallback(FalseOnContainerNotFound.class) - ListenableFuture<Boolean> containerExists( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); - - /** - * @see AzureBlobClient#setResourceMetadata - */ - @Named("SetContainerMetadata") - @PUT - @Path("{container}") - @QueryParams(keys = { "restype", "comp" }, values = { "container", "metadata" }) - ListenableFuture<Void> setResourceMetadata( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata); - - /** - * @see AzureBlobClient#deleteContainer - */ - @Named("DeleteContainer") - @DELETE - @Path("{container}") - @Fallback(VoidOnNotFoundOr404.class) - @QueryParams(keys = "restype", values = "container") - ListenableFuture<Void> deleteContainer( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); - - /** - * @see AzureBlobClient#createRootContainer - */ - @Named("CreateContainer") - @PUT - @Path("$root") - @Fallback(FalseIfContainerAlreadyExists.class) - @QueryParams(keys = "restype", values = "container") - ListenableFuture<Boolean> createRootContainer(CreateContainerOptions... options); - - /** - * @see AzureBlobClient#deleteRootContainer - */ - @Named("DeleteContainer") - @DELETE - @Path("$root") - @Fallback(TrueOnNotFoundOr404.class) - @QueryParams(keys = "restype", values = "container") - ListenableFuture<Void> deleteRootContainer(); - - /** - * @see AzureBlobClient#listBlobs(String, ListBlobsOptions[]) - */ - @Named("ListBlobs") - @GET - @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) - @Path("{container}") - @QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) - ListenableFuture<ListBlobsResponse> listBlobs( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - ListBlobsOptions... options); - - /** - * @see AzureBlobClient#listBlobs(ListBlobsOptions[]) - */ - @Named("ListBlobs") - @GET - @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) - @Path("$root") - @QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) - ListenableFuture<ListBlobsResponse> listBlobs(ListBlobsOptions... options); - - /** - * @see AzureBlobClient#putBlob - */ - @Named("PutBlob") - @PUT - @Path("{container}/{name}") - @Headers(keys = EXPECT, values = "100-continue") - @ResponseParser(ParseETagHeader.class) - ListenableFuture<String> putBlob( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") @ParamParser(BlobName.class) @BinderParam(BindAzureBlobMetadataToRequest.class) org.jclouds.azureblob.domain.AzureBlob object); - - /** - * @see AzureBlobClient#getBlob - */ - @Named("GetBlob") - @GET - @ResponseParser(ParseBlobFromHeadersAndHttpContent.class) - @Fallback(NullOnKeyNotFound.class) - @Path("{container}/{name}") - ListenableFuture<org.jclouds.azureblob.domain.AzureBlob> getBlob( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name, GetOptions... options); - - /** - * @see AzureBlobClient#getBlobProperties - */ - @Named("GetBlobProperties") - @HEAD - @ResponseParser(ParseBlobPropertiesFromHeaders.class) - @Fallback(NullOnKeyNotFound.class) - @Path("{container}/{name}") - ListenableFuture<BlobProperties> getBlobProperties( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name); - - /** - * @see AzureBlobClient#blobExists - * - */ - @Named("GetBlobProperties") - @HEAD - @Fallback(FalseOnKeyNotFound.class) - @Path("{container}/{name}") - ListenableFuture<Boolean> blobExists( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name); - - /** - * @see AzureBlobClient#setBlobMetadata - */ - @Named("SetBlobMetadata") - @PUT - @Path("{container}/{name}") - @QueryParams(keys = { "comp" }, values = { "metadata" }) - ListenableFuture<Void> setBlobMetadata( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name, @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata); - - /** - * @see AzureBlobClient#deleteBlob - */ - @Named("DeleteBlob") - @DELETE - @Fallback(VoidOnNotFoundOr404.class) - @Path("{container}/{name}") - ListenableFuture<Void> deleteBlob( - @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name); - - - /** - * @see AzureBlobClient#putBlock - */ - @Named("PutBlock") - @PUT - @Path("{container}/{name}") - @QueryParams(keys = { "comp" }, values = { "block" }) - ListenableFuture<Void> putBlock(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name, - @QueryParam("blockid") @ParamValidators(BlockIdValidator.class) String blockId, Payload part); - - - /** - * @see AzureBlobClient#putBlockList - */ - @Named("PutBlockList") - @PUT - @Path("{container}/{name}") - @ResponseParser(ParseETagHeader.class) - @QueryParams(keys = { "comp" }, values = { "blocklist" }) - ListenableFuture<String> putBlockList(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name, - @BinderParam(BindAzureBlocksToRequest.class) List<String> blockIdList); - - /** - * @see AzureBlobClient#getBlockList - */ - @Named("GetBlockList") - @GET - @Path("{container}/{name}") - @XMLResponseParser(BlobBlocksResultsHandler.class) - @QueryParams(keys = { "comp" }, values = { "blocklist" }) - ListenableFuture<ListBlobBlocksResponse> getBlockList(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, - @PathParam("name") String name); - -} http://git-wip-us.apache.org/repos/asf/jclouds/blob/4c95a578/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java ---------------------------------------------------------------------- diff --git a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java index 4d78ebb..790a0f4 100644 --- a/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java +++ b/providers/azureblob/src/main/java/org/jclouds/azureblob/AzureBlobClient.java @@ -16,32 +16,75 @@ */ package org.jclouds.azureblob; +import static com.google.common.net.HttpHeaders.EXPECT; +import static org.jclouds.Fallbacks.TrueOnNotFoundOr404; +import static org.jclouds.Fallbacks.VoidOnNotFoundOr404; +import static org.jclouds.azureblob.AzureBlobFallbacks.FalseIfContainerAlreadyExists; +import static org.jclouds.blobstore.BlobStoreFallbacks.FalseOnContainerNotFound; +import static org.jclouds.blobstore.BlobStoreFallbacks.FalseOnKeyNotFound; +import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnContainerNotFound; +import static org.jclouds.blobstore.BlobStoreFallbacks.NullOnKeyNotFound; + +import java.io.Closeable; import java.util.List; import java.util.Map; + +import javax.inject.Named; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.QueryParam; + import org.jclouds.azure.storage.domain.BoundedSet; +import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication; import org.jclouds.azure.storage.options.ListOptions; +import org.jclouds.azure.storage.reference.AzureStorageHeaders; +import org.jclouds.azureblob.binders.BindAzureBlobMetadataToRequest; +import org.jclouds.azureblob.binders.BindAzureBlocksToRequest; import org.jclouds.azureblob.domain.AzureBlob; import org.jclouds.azureblob.domain.BlobProperties; import org.jclouds.azureblob.domain.ContainerProperties; import org.jclouds.azureblob.domain.ListBlobBlocksResponse; import org.jclouds.azureblob.domain.ListBlobsResponse; import org.jclouds.azureblob.domain.PublicAccess; +import org.jclouds.azureblob.functions.BlobName; +import org.jclouds.azureblob.functions.ParseBlobFromHeadersAndHttpContent; +import org.jclouds.azureblob.functions.ParseBlobPropertiesFromHeaders; +import org.jclouds.azureblob.functions.ParseContainerPropertiesFromHeaders; +import org.jclouds.azureblob.functions.ParsePublicAccessHeader; import org.jclouds.azureblob.options.CreateContainerOptions; import org.jclouds.azureblob.options.ListBlobsOptions; +import org.jclouds.azureblob.predicates.validators.BlockIdValidator; +import org.jclouds.azureblob.predicates.validators.ContainerNameValidator; +import org.jclouds.azureblob.xml.AccountNameEnumerationResultsHandler; +import org.jclouds.azureblob.xml.BlobBlocksResultsHandler; +import org.jclouds.azureblob.xml.ContainerNameEnumerationResultsHandler; +import org.jclouds.blobstore.binders.BindMapToHeadersWithPrefix; +import org.jclouds.http.functions.ParseETagHeader; import org.jclouds.http.options.GetOptions; +import org.jclouds.io.Payload; +import org.jclouds.rest.annotations.BinderParam; +import org.jclouds.rest.annotations.Fallback; +import org.jclouds.rest.annotations.Headers; +import org.jclouds.rest.annotations.ParamParser; +import org.jclouds.rest.annotations.ParamValidators; +import org.jclouds.rest.annotations.QueryParams; +import org.jclouds.rest.annotations.RequestFilters; +import org.jclouds.rest.annotations.ResponseParser; +import org.jclouds.rest.annotations.SkipEncoding; +import org.jclouds.rest.annotations.XMLResponseParser; import com.google.inject.Provides; -import org.jclouds.io.Payload; -/** - * Provides access to Azure Blob via their REST API. - * <p/> - * All commands return a Future of the result from Azure Blob. Any exceptions incurred during - * processing will be backend in an {@link ExecutionException} as documented in {@link Future#get()}. - * - * @see <a href="http://msdn.microsoft.com/en-us/library/dd135733.aspx" /> - */ -public interface AzureBlobClient { +/** Provides access to Azure Blob via their REST API. */ +@RequestFilters(SharedKeyLiteAuthentication.class) +@Headers(keys = AzureStorageHeaders.VERSION, values = "{jclouds.api-version}") +@SkipEncoding({ '/', '$' }) +@Path("/") +public interface AzureBlobClient extends Closeable { @Provides AzureBlob newBlob(); @@ -54,8 +97,13 @@ public interface AzureBlobClient { * controls the number or type of results requested * @see ListOptions */ + @Named("ListContainers") + @GET + @XMLResponseParser(AccountNameEnumerationResultsHandler.class) + @QueryParams(keys = "comp", values = "list") BoundedSet<ContainerProperties> listContainers(ListOptions... listOptions); + /** * The Create Container operation creates a new container under the specified identity. If the * container with the same name already exists, the operation fails. @@ -68,18 +116,38 @@ public interface AzureBlobClient { * @see CreateContainerOptions * */ - boolean createContainer(String container, CreateContainerOptions... options); + @Named("CreateContainer") + @PUT + @Path("{container}") + @Fallback(FalseIfContainerAlreadyExists.class) + @QueryParams(keys = "restype", values = "container") + boolean createContainer(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + CreateContainerOptions... options); + /** * The Get Container Properties operation returns all user-defined metadata and system properties * for the specified container. The data returned does not include the container's list of blobs. */ - ContainerProperties getContainerProperties(String container); + @Named("GetContainerProperties") + @HEAD + @Path("{container}") + @QueryParams(keys = "restype", values = "container") + @ResponseParser(ParseContainerPropertiesFromHeaders.class) + @Fallback(NullOnContainerNotFound.class) + ContainerProperties getContainerProperties( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); + /** * Issues a HEAD command to determine if the container exists or not. */ - boolean containerExists(String container); + @Named("GetContainerProperties") + @HEAD + @Path("{container}") + @QueryParams(keys = "restype", values = "container") + @Fallback(FalseOnContainerNotFound.class) + boolean containerExists(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); /** * The Set Container Metadata operation sets one or more user-defined name/value pairs for the @@ -93,7 +161,14 @@ public interface AzureBlobClient { * <p/> * Calling Set Container Metadata updates the ETag for the container. */ - void setResourceMetadata(String container, Map<String, String> metadata); + @Named("SetContainerMetadata") + @PUT + @Path("{container}") + @QueryParams(keys = { "restype", "comp" }, values = { "container", "metadata" }) + void setResourceMetadata( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata); + /** * The Delete Container operation marks the specified container for deletion. The container and @@ -108,7 +183,12 @@ public interface AzureBlobClient { * 404 (Not Found) while the container is being deleted. * */ - void deleteContainer(String container); + @Named("DeleteContainer") + @DELETE + @Path("{container}") + @Fallback(VoidOnNotFoundOr404.class) + @QueryParams(keys = "restype", values = "container") + void deleteContainer(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); /** * The root container is a default container that may be inferred from a URL requesting a blob @@ -121,15 +201,25 @@ public interface AzureBlobClient { * @see CreateContainerOptions * */ + @Named("CreateContainer") + @PUT + @Path("$root") + @Fallback(FalseIfContainerAlreadyExists.class) + @QueryParams(keys = "restype", values = "container") boolean createRootContainer(CreateContainerOptions... options); /** - * - * - * @param container - * @return whether data in the container may be accessed publicly and the level of access + * Returns whether data in the container may be accessed publicly and the level of access */ - PublicAccess getPublicAccessForContainer(String container); + @Named("GetContainerACL") + @HEAD + @Path("{container}") + @QueryParams(keys = { "restype", "comp" }, values = { "container", "acl" }) + @ResponseParser(ParsePublicAccessHeader.class) + @Fallback(NullOnContainerNotFound.class) + PublicAccess getPublicAccessForContainer( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container); + /** * The Delete Container operation marks the specified container for deletion. The container and @@ -142,9 +232,14 @@ public interface AzureBlobClient { * operations, including operations on any blobs under the container, will fail with status code * 404 (Not Found) while the container is being deleted. * - * @see deleteContainer(String) - * @see createRootContainer(CreateContainerOptions) + * @see #deleteContainer(String) + * @see #createRootContainer(CreateContainerOptions...) */ + @Named("DeleteContainer") + @DELETE + @Path("$root") + @Fallback(TrueOnNotFoundOr404.class) + @QueryParams(keys = "restype", values = "container") void deleteRootContainer(); /** @@ -182,8 +277,20 @@ public interface AzureBlobClient { * <p/> * Blobs are listed in alphabetical order in the response body. */ - ListBlobsResponse listBlobs(String container, ListBlobsOptions... options); + @Named("ListBlobs") + @GET + @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) + @Path("{container}") + @QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) + ListBlobsResponse listBlobs(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + ListBlobsOptions... options); + + @Named("ListBlobs") + @GET + @XMLResponseParser(ContainerNameEnumerationResultsHandler.class) + @Path("$root") + @QueryParams(keys = { "restype", "comp" }, values = { "container", "list" }) ListBlobsResponse listBlobs(ListBlobsOptions... options); /** @@ -201,57 +308,107 @@ public interface AzureBlobClient { * (Request Payload Too Large). The Blob service also returns additional information about the * error in the response, including the maximum blob size permitted in bytes. */ - String putBlob(String container, AzureBlob object); + @Named("PutBlob") + @PUT + @Path("{container}/{name}") + @Headers(keys = EXPECT, values = "100-continue") + @ResponseParser(ParseETagHeader.class) + String putBlob(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") @ParamParser(BlobName.class) @BinderParam(BindAzureBlobMetadataToRequest.class) + AzureBlob object); + /** * The Get Blob operation reads or downloads a blob from the system, including its metadata and * properties. */ - AzureBlob getBlob(String container, String name, GetOptions... options); + @Named("GetBlob") + @GET + @ResponseParser(ParseBlobFromHeadersAndHttpContent.class) + @Fallback(NullOnKeyNotFound.class) + @Path("{container}/{name}") + AzureBlob getBlob(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name, GetOptions... options); /** * The Put Block operation creates a block blob on Azure which can be later assembled into * a single, large blob object with the Put Block List operation. - * - * @see <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135726.aspx">Put Blob</a> */ - void putBlock(String container, String name, String blockId, Payload object); + @Named("PutBlock") + @PUT + @Path("{container}/{name}") + @QueryParams(keys = { "comp" }, values = { "block" }) + void putBlock(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name, + @QueryParam("blockid") @ParamValidators(BlockIdValidator.class) String blockId, Payload part); /** * The Put Block List assembles a list of blocks previously uploaded with Put Block into a single * blob. Blocks are either already committed to a blob or uncommitted. The blocks ids passed here * are searched for first in the uncommitted block list; then committed using the "latest" strategy. - * - * @see <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179467.aspx">Put Block List</a> */ - String putBlockList(String container, String name, List<String> blockIdList); + @Named("PutBlockList") + @PUT + @Path("{container}/{name}") + @ResponseParser(ParseETagHeader.class) + @QueryParams(keys = { "comp" }, values = { "blocklist" }) + String putBlockList(@PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name, + @BinderParam(BindAzureBlocksToRequest.class) List<String> blockIdList); + + @Named("GetBlockList") + @GET + @Path("{container}/{name}") + @XMLResponseParser(BlobBlocksResultsHandler.class) + @QueryParams(keys = { "comp" }, values = { "blocklist" }) + ListBlobBlocksResponse getBlockList( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name); - /** - * Get Block ID List for a blob - * - * @see <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179400.aspx">Get Block List</a> - */ - ListBlobBlocksResponse getBlockList(String container, String name); /** * The Get Blob Properties operation returns all user-defined metadata, standard HTTP properties, * and system properties for the blob. It does not return the content of the blob. */ - BlobProperties getBlobProperties(String container, String name); + @Named("GetBlobProperties") + @HEAD + @ResponseParser(ParseBlobPropertiesFromHeaders.class) + @Fallback(NullOnKeyNotFound.class) + @Path("{container}/{name}") + BlobProperties getBlobProperties( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name); - void setBlobMetadata(String container, String name, Map<String, String> metadata); + + @Named("SetBlobMetadata") + @PUT + @Path("{container}/{name}") + @QueryParams(keys = { "comp" }, values = { "metadata" }) + void setBlobMetadata( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name, @BinderParam(BindMapToHeadersWithPrefix.class) Map<String, String> metadata); /** * The Delete Blob operation marks the specified blob for deletion. The blob is later deleted * during garbage collection. */ - void deleteBlob(String container, String name); - + @Named("DeleteBlob") + @DELETE + @Fallback(VoidOnNotFoundOr404.class) + @Path("{container}/{name}") + void deleteBlob( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name); /** - * @throws ContainerNotFoundException - * if the container is not present. + * @throws org.jclouds.blobstore.ContainerNotFoundException if the container is not present. */ - boolean blobExists(String container, String name); + @Named("GetBlobProperties") + @HEAD + @Fallback(FalseOnKeyNotFound.class) + @Path("{container}/{name}") + boolean blobExists( + @PathParam("container") @ParamValidators(ContainerNameValidator.class) String container, + @PathParam("name") String name); }
