/**
* Copyright Microsoft Corporation
*
* Licensed 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 com.microsoft.azure.storage.core;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.Map.Entry;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.RequestOptions;
import com.microsoft.azure.storage.StorageException;
/**
* RESERVED FOR INTERNAL USE. The Base Request class for the protocol layer.
*/
public final class BaseRequest {
private static final String METADATA = "metadata";
private static final String SERVICE = "service";
private static final String STATS = "stats";
private static final String TIMEOUT = "timeout";
/**
* Stores the user agent to send over the wire to identify the client.
*/
private static String userAgent;
/**
* Adds the metadata.
*
* @param request
* The request.
* @param metadata
* The metadata.
*/
public static void addMetadata(final HttpURLConnection request, final Map<String, String> metadata,
final OperationContext opContext) {
if (metadata != null) {
for (final Entry<String, String> entry : metadata.entrySet()) {
addMetadata(request, entry.getKey(), entry.getValue(), opContext);
}
}
}
/**
* Adds the metadata.
*
* @param opContext
* an object used to track the execution of the operation
* @param request
* The request.
* @param name
* The metadata name.
* @param value
* The metadata value.
*/
private static void addMetadata(final HttpURLConnection request, final String name, final String value,
final OperationContext opContext) {
if (Utility.isNullOrEmptyOrWhitespace(name)) {
throw new IllegalArgumentException(SR.METADATA_KEY_INVALID);
}
else if (Utility.isNullOrEmptyOrWhitespace(value)) {
throw new IllegalArgumentException(SR.METADATA_VALUE_INVALID);
}
request.setRequestProperty(Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA + name, value);
}
/**
* Adds the optional header.
*
* @param request
* a HttpURLConnection for the operation.
* @param name
* the metadata name.
* @param value
* the metadata value.
*/
public static void addOptionalHeader(final HttpURLConnection request, final String name, final String value) {
if (value != null && !value.equals(Constants.EMPTY_STRING)) {
request.setRequestProperty(name, value);
}
}
/**
* Creates the specified resource. Note request is set to setFixedLengthStreamingMode(0); Sign with 0 length.
*
* @param uri
* the request Uri.
* @param options
* A {@link RequestOptions} object that specifies execution options such as retry policy and timeout
* settings for the operation.
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
*
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
* @throws IllegalArgumentException
*/
public static HttpURLConnection create(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setFixedLengthStreamingMode(0);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* Creates the web request.
*
* @param uri
* the request Uri.
* @param options
* A {@link RequestOptions} object that specifies execution options such as retry policy and timeout
* settings for the operation. This parameter is unused.
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
*/
public static HttpURLConnection createURLConnection(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
if (options.getTimeoutIntervalInMs() != null && options.getTimeoutIntervalInMs() != 0) {
builder.add(TIMEOUT, String.valueOf(options.getTimeoutIntervalInMs() / 1000));
}
final URL resourceUrl = builder.addToURI(uri).toURL();
// Get the proxy settings
Proxy proxy = OperationContext.getDefaultProxy();
if (opContext != null && opContext.getProxy() != null) {
proxy = opContext.getProxy();
}
// Set up connection, optionally with proxy settings
final HttpURLConnection retConnection;
if (proxy != null) {
retConnection = (HttpURLConnection) resourceUrl.openConnection(proxy);
}
else {
retConnection = (HttpURLConnection) resourceUrl.openConnection();
}
/*
* ReadTimeout must be explicitly set to avoid a bug in JDK 6. In certain cases, this bug causes an immediate
* read timeout exception to be thrown even if ReadTimeout is not set.
*
* Both connect and read timeout are set to the same value as we have no way of knowing how to partition
* the remaining time between these operations. The user can override these timeouts using the SendingRequest
* event handler if more control is desired.
*/
int timeout = Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs(), options.getTimeoutIntervalInMs());
retConnection.setReadTimeout(timeout);
retConnection.setConnectTimeout(timeout);
// Note : by default sends Accept behavior as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, Constants.HeaderConstants.XML_TYPE);
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, Constants.UTF8_CHARSET);
// Note: by default sends Accept-Encoding as gzip
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_ENCODING, Constants.EMPTY_STRING);
// Note : by default sends Content-type behavior as application/x-www-form-urlencoded for posts.
retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, Constants.EMPTY_STRING);
retConnection.setRequestProperty(Constants.HeaderConstants.STORAGE_VERSION_HEADER,
Constants.HeaderConstants.TARGET_STORAGE_VERSION);
retConnection.setRequestProperty(Constants.HeaderConstants.USER_AGENT, getUserAgent());
retConnection.setRequestProperty(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER,
opContext.getClientRequestID());
return retConnection;
}
/**
* Deletes the specified resource. Sign with no length specified.
*
* @param uri
* the request Uri.
* @param timeout
* the timeout for the request
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
*/
public static HttpURLConnection delete(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_DELETE);
return retConnection;
}
/**
* Gets a {@link UriQueryBuilder} for listing.
*
* @param listingContext
* A {@link ListingContext} object that specifies parameters for
* the listing operation, if any. May be <code>null</code>.
*
* @throws StorageException
* If a storage service error occurred during the operation.
*/
public static UriQueryBuilder getListUriQueryBuilder(final ListingContext listingContext) throws StorageException {
final UriQueryBuilder builder = new UriQueryBuilder();
builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.LIST);
if (listingContext != null) {
if (!Utility.isNullOrEmpty(listingContext.getPrefix())) {
builder.add(Constants.QueryConstants.PREFIX, listingContext.getPrefix());
}
if (!Utility.isNullOrEmpty(listingContext.getMarker())) {
builder.add(Constants.QueryConstants.MARKER, listingContext.getMarker());
}
if (listingContext.getMaxResults() != null && listingContext.getMaxResults() > 0) {
builder.add(Constants.QueryConstants.MAX_RESULTS, listingContext.getMaxResults().toString());
}
}
return builder;
}
/**
* Gets the properties. Sign with no length specified.
*
* @param uri
* The Uri to query.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws StorageException
* @throws URISyntaxException
* @throws IOException
* */
public static HttpURLConnection getProperties(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_HEAD);
return retConnection;
}
/**
* Creates a HttpURLConnection used to retrieve the Analytics service properties from the storage service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection getServiceProperties(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_GET);
return retConnection;
}
/**
* Creates a web request to get the stats of the service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection getServiceStats(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, STATS);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod("GET");
return retConnection;
}
/**
* Gets the user agent to send over the wire to identify the client.
*
* @return the user agent to send over the wire to identify the client.
*/
public static String getUserAgent() {
if (userAgent == null) {
String userAgentComment = String.format(Utility.LOCALE_US, "(Android %s; %s; %s)",
android.os.Build.VERSION.RELEASE, android.os.Build.BRAND, android.os.Build.MODEL);
userAgent = String.format("%s/%s %s", Constants.HeaderConstants.USER_AGENT_PREFIX,
Constants.HeaderConstants.USER_AGENT_VERSION, userAgentComment);
}
return userAgent;
}
/**
* Sets the metadata. Sign with 0 length.
*
* @param uri
* The blob Uri.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws StorageException
* @throws URISyntaxException
* @throws IOException
* */
public static HttpURLConnection setMetadata(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, METADATA);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setFixedLengthStreamingMode(0);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* Creates a HttpURLConnection used to set the Analytics service properties on the storage service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection setServiceProperties(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* A private default constructor. All methods of this class are static so no instances of it should ever be created.
*/
private BaseRequest() {
// No op
}
}