All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.windowsazure.services.table.client.TableBatchOperation Maven / Gradle / Ivy

There is a newer version: 0.4.6
Show newest version
/**
 * 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.windowsazure.services.table.client;

import java.io.InputStream;
import java.io.StringReader;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.UUID;

import javax.xml.stream.XMLStreamReader;

import com.microsoft.windowsazure.services.core.storage.Constants;
import com.microsoft.windowsazure.services.core.storage.OperationContext;
import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings;
import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.core.storage.utils.Utility;
import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine;
import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation;

/**
 * A class which represents a batch operation. A batch operation is a collection of table operations which are executed
 * by the Storage Service REST API as a single atomic operation, by invoking an Entity Group Transaction.
 * 

* A batch operation may contain up to 100 individual table operations, with the requirement that each operation entity * must have same partition key. A batch with a retrieve operation cannot contain any other operations. Note that the * total payload of a batch operation is limited to 4MB. */ public class TableBatchOperation extends ArrayList { private static final long serialVersionUID = -1192644463287355790L; private boolean hasQuery = false; private String partitionKey = null; /** * Adds the table operation at the specified index in the batch operation ArrayList. * * @param index * The index in the batch operation ArrayList to add the table operation at. * @param element * The {@link TableOperation} to add to the batch operation. */ @Override public void add(final int index, final TableOperation element) { Utility.assertNotNull("element", element); this.checkSingleQueryPerBatch(element); if (element.getOperationType() == TableOperationType.RETRIEVE) { this.lockToPartitionKey(((QueryTableOperation) element).getPartitionKey()); } else { this.lockToPartitionKey(element.getEntity().getPartitionKey()); } super.add(index, element); } /** * Adds the table operation to the batch operation ArrayList. * * @param element * The {@link TableOperation} to add to the batch operation. * @return * true if the operation was added successfully. */ @Override public boolean add(final TableOperation element) { Utility.assertNotNull("element", element); this.checkSingleQueryPerBatch(element); if (element.getEntity() == null) { // Query operation this.lockToPartitionKey(((QueryTableOperation) element).getPartitionKey()); } else { this.lockToPartitionKey(element.getEntity().getPartitionKey()); } return super.add(element); } /** * Adds the collection of table operations to the batch operation ArrayList starting at the specified * index. * * @param index * The index in the batch operation ArrayList to add the table operation at. * @param c * The collection of {@link TableOperation} objects to add to the batch operation. * @return * true if the operations were added successfully. */ @Override public boolean addAll(final int index, final java.util.Collection c) { for (final TableOperation operation : c) { Utility.assertNotNull("operation", operation); this.checkSingleQueryPerBatch(operation); if (operation.getEntity() == null) { // Query operation this.lockToPartitionKey(((QueryTableOperation) operation).getPartitionKey()); } else { this.lockToPartitionKey(operation.getEntity().getPartitionKey()); } } return super.addAll(index, c); } /** * Adds the collection of table operations to the batch operation ArrayList. * * @param c * The collection of {@link TableOperation} objects to add to the batch operation. * @return * true if the operations were added successfully. */ @Override public boolean addAll(final java.util.Collection c) { for (final TableOperation operation : c) { Utility.assertNotNull("operation", operation); this.checkSingleQueryPerBatch(operation); if (operation.getEntity() == null) { // Query operation this.lockToPartitionKey(((QueryTableOperation) operation).getPartitionKey()); } else { this.lockToPartitionKey(operation.getEntity().getPartitionKey()); } } return super.addAll(c); } /** * Clears all table operations from the batch operation. */ @Override public void clear() { super.clear(); checkResetEntityLocks(); } /** * Adds a table operation to delete the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to delete. */ public void delete(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.delete(entity)); } /** * Adds a table operation to insert the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to insert. */ public void insert(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.insert(entity)); } /** * Adds a table operation to insert or merge the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to insert if not found or to merge if it exists. */ public void insertOrMerge(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.insertOrMerge(entity)); } /** * Adds a table operation to insert or replace the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to insert if not found or to replace if it exists. */ public void insertOrReplace(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.insertOrReplace(entity)); } /** * Adds a table operation to merge the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to merge. */ public void merge(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.merge(entity)); } /** * Adds a table operation to retrieve an entity of the specified class type with the specified PartitionKey and * RowKey to the batch operation. * * @param partitionKey * A String containing the PartitionKey of the entity to retrieve. * @param rowKey * A String containing the RowKey of the entity to retrieve. * @param clazzType * The class of the {@link TableEntity} type for the entity to retrieve. */ public void retrieve(final String partitionKey, final String rowKey, final Class clazzType) { this.lockToPartitionKey(partitionKey); this.add(TableOperation.retrieve(partitionKey, rowKey, clazzType)); } /** * Adds a table operation to retrieve an entity of the specified class type with the specified PartitionKey and * RowKey to the batch operation. * * @param partitionKey * A String containing the PartitionKey of the entity to retrieve. * @param rowKey * A String containing the RowKey of the entity to retrieve. * @param resolver * The {@link EntityResolver} implementation to project the entity to retrieve as a particular type in * the result. */ public void retrieve(final String partitionKey, final String rowKey, final EntityResolver resolver) { this.lockToPartitionKey(partitionKey); this.add(TableOperation.retrieve(partitionKey, rowKey, resolver)); } /** * Removes the table operation at the specified index from the batch operation. * * @param index * The index in the ArrayList of the table operation to remove from the batch operation. */ @Override public TableOperation remove(int index) { TableOperation op = super.remove(index); checkResetEntityLocks(); return op; } /** * Removes the specified Object from the batch operation. * * @param o * The Object to remove from the batch operation. * @return * true if the object was removed successfully. */ @Override public boolean remove(Object o) { boolean ret = super.remove(o); checkResetEntityLocks(); return ret; } /** * Removes all elements of the specified collection from the batch operation. * * @param c * The collection of elements to remove from the batch operation. * @return * true if the objects in the collection were removed successfully. */ @Override public boolean removeAll(java.util.Collection c) { boolean ret = super.removeAll(c); checkResetEntityLocks(); return ret; } /** * Adds a table operation to replace the specified entity to the batch operation. * * @param entity * The {@link TableEntity} to replace. */ public void replace(final TableEntity entity) { this.lockToPartitionKey(entity.getPartitionKey()); this.add(TableOperation.replace(entity)); } /** * Reserved for internal use. Clears internal fields when the batch operation is empty. */ private void checkResetEntityLocks() { if (this.size() == 0) { this.partitionKey = null; this.hasQuery = false; } } /** * Reserved for internal use. Verifies that the batch operation either contains no retrieve operations, or contains * only a single retrieve operation. * * @param op * The {@link TableOperation} to be added if the verification succeeds. */ private void checkSingleQueryPerBatch(final TableOperation op) { // if this has a query then no other operations can be added. if (this.hasQuery) { throw new IllegalArgumentException( "A batch transaction with a retrieve operation cannot contain any other operations."); } if (op.opType == TableOperationType.RETRIEVE) { if (this.size() > 0) { throw new IllegalArgumentException( "A batch transaction with a retrieve operation cannot contain any other operations."); } else { this.hasQuery = true; } } } /** * Reserved for internal use. Verifies that the specified PartitionKey value matches the value in the batch * operation. * * @param partitionKey * The String containing the PartitionKey value to check. */ private void lockToPartitionKey(final String partitionKey) { if (this.partitionKey == null) { this.partitionKey = partitionKey; } else { if (partitionKey.length() != partitionKey.length() || !this.partitionKey.equals(partitionKey)) { throw new IllegalArgumentException("All entities in a given batch must have the same partition key."); } } } /** * Reserved for internal use. Executes this batch operation on the specified table, using the specified * {@link TableRequestOptions} and {@link OperationContext}. *

* This method will invoke the Storage Service REST API to execute this batch operation, using the Table service * endpoint and storage account credentials in the {@link CloudTableClient} object. * * @param client * A {@link CloudTableClient} instance specifying the Table service endpoint and storage account * credentials to use. * @param tableName * A String containing the name of the table. * @param options * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout * settings for the operation. * @param opContext * An {@link OperationContext} object for tracking the current operation. * * @return * An ArrayList of {@link TableResult} containing the results of executing the operation. * * @throws StorageException * if an error occurs in the storage operation. */ protected ArrayList execute(final CloudTableClient client, final String tableName, final TableRequestOptions options, final OperationContext opContext) throws StorageException { Utility.assertNotNullOrEmpty("TableName", tableName); if (this.size() == 0) { throw new IllegalArgumentException("Cannot Execute an empty batch operation"); } final StorageOperation> impl = new StorageOperation>( options) { @Override public ArrayList execute(final CloudTableClient client, final TableBatchOperation batch, final OperationContext opContext) throws Exception { final String batchID = String.format("batch_%s", UUID.randomUUID().toString()); final String changeSet = String.format("changeset_%s", UUID.randomUUID().toString()); final HttpURLConnection request = TableRequest.batch(client.getTransformedEndPoint(opContext), options.getTimeoutIntervalInMs(), batchID, null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); MimeHelper.writeBatchToStream(request.getOutputStream(), tableName, batch, batchID, changeSet, opContext); final InputStream streamRef = ExecutionEngine.getInputStream(request, opContext, this.getResult()); ArrayList responseParts = null; try { final String contentType = request.getHeaderField(Constants.HeaderConstants.CONTENT_TYPE); final String[] headerVals = contentType.split("multipart/mixed; boundary="); if (headerVals == null || headerVals.length != 2) { throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, "An incorrect Content-type was returned from the server.", Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } responseParts = MimeHelper.readBatchResponseStream(streamRef, headerVals[1], opContext); } finally { streamRef.close(); } ExecutionEngine.getResponseCode(this.getResult(), request, opContext); if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { this.setNonExceptionedRetryableFailure(true); return null; } final ArrayList result = new ArrayList(); for (int m = 0; m < batch.size(); m++) { final TableOperation currOp = batch.get(m); final MimePart currMimePart = responseParts.get(m); boolean failFlag = false; // Validate response if (currOp.opType == TableOperationType.INSERT) { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { throw new TableServiceException(currMimePart.httpStatusCode, currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload)); } // Insert should receive created. if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_CREATED) { failFlag = true; } } else if (currOp.opType == TableOperationType.RETRIEVE) { if (currMimePart.httpStatusCode == HttpURLConnection.HTTP_NOT_FOUND) { // Empty result result.add(new TableResult(currMimePart.httpStatusCode)); return result; } // Point query should receive ok. if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_OK) { failFlag = true; } } else { // Validate response code. if (currMimePart.httpStatusCode == HttpURLConnection.HTTP_NOT_FOUND) { // Throw so as to not retry. throw new TableServiceException(currMimePart.httpStatusCode, currMimePart.httpStatusMessage, currOp, new StringReader(currMimePart.payload)); } if (currMimePart.httpStatusCode != HttpURLConnection.HTTP_NO_CONTENT) { // All others should receive no content. (delete, merge, upsert etc) failFlag = true; } } if (failFlag) { TableServiceException potentiallyRetryableException = new TableServiceException( currMimePart.httpStatusCode, currMimePart.httpStatusMessage, currOp, new StringReader( currMimePart.payload)); potentiallyRetryableException.setRetryable(true); throw potentiallyRetryableException; } XMLStreamReader xmlr = null; if (currOp.opType == TableOperationType.INSERT || currOp.opType == TableOperationType.RETRIEVE) { xmlr = Utility.createXMLStreamReaderFromReader(new StringReader(currMimePart.payload)); } result.add(currOp.parseResponse(xmlr, currMimePart.httpStatusCode, currMimePart.headers.get(TableConstants.HeaderConstants.ETAG), opContext)); } return result; } }; return ExecutionEngine.executeWithRetry(client, this, impl, options.getRetryPolicyFactory(), opContext); } /** * Reserved for internal use. Removes all the table operations at indexes in the specified range from the batch * operation ArrayList. * * @param fromIndex * The inclusive lower bound of the range of {@link TableOperation} objects to remove from the batch * operation ArrayList. * @param toIndex * The exclusive upper bound of the range of {@link TableOperation} objects to remove from the batch * operation ArrayList. */ @Override protected void removeRange(int fromIndex, int toIndex) { super.removeRange(fromIndex, toIndex); checkResetEntityLocks(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy