/** * Reserved for internal use. Execute this table operation on the specified table, using the * specified {@link TableRequestOptions} and {@link OperationContext}. * * <p>This method will invoke the Storage Service REST API to execute this table 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, * storage account credentials, and any additional query parameters. * @param tableName A <code>String</code> 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 A {@link TableResult} containing the results of executing the operation. * @throws StorageException if an error occurs in the storage operation. */ protected TableResult execute( final CloudTableClient client, final String tableName, TableRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } if (options == null) { options = new TableRequestOptions(); } opContext.initialize(); options.applyDefaults(client); Utility.assertNotNullOrEmpty("TableName", tableName); if (this.getOperationType() == TableOperationType.INSERT || this.getOperationType() == TableOperationType.INSERT_OR_MERGE || this.getOperationType() == TableOperationType.INSERT_OR_REPLACE) { return this.performInsert(client, tableName, options, opContext); } else if (this.getOperationType() == TableOperationType.DELETE) { return this.performDelete(client, tableName, options, opContext); } else if (this.getOperationType() == TableOperationType.MERGE) { return this.performMerge(client, tableName, options, opContext); } else if (this.getOperationType() == TableOperationType.REPLACE) { return this.performUpdate(client, tableName, options, opContext); } else if (this.getOperationType() == TableOperationType.RETRIEVE) { return ((QueryTableOperation) this).performRetrieve(client, tableName, options, opContext); } else { throw new IllegalArgumentException("Unknown table operation"); } }
/** * Reserved for internal use. Perform a merge operation on the specified table, using the * specified {@link TableRequestOptions} and {@link OperationContext}. * * <p>This method will invoke the Merge Entity REST API to execute this table 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, * storage account credentials, and any additional query parameters. * @param tableName A <code>String</code> 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 A {@link TableResult} containing the results of executing the operation. * @throws StorageException if an error occurs in the storage operation. */ private TableResult performMerge( final CloudTableClient client, final String tableName, final TableRequestOptions options, final OperationContext opContext) throws StorageException { Utility.assertNotNullOrEmpty("Merge requires a valid ETag", this.getEntity().getEtag()); Utility.assertNotNullOrEmpty( "Merge requires a valid PartitionKey", this.getEntity().getPartitionKey()); Utility.assertNotNullOrEmpty("Merge requires a valid RowKey", this.getEntity().getRowKey()); final StorageOperation<CloudTableClient, TableOperation, TableResult> impl = new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) { @Override public TableResult execute( final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.merge( client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); AtomPubParser.writeSingleEntityToStream( operation.getEntity(), false, request.getOutputStream(), opContext); this.setResult(ExecutionEngine.processRequest(request, opContext)); if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { throw TableServiceException.generateTableServiceException( false, this.getResult(), operation, request.getErrorStream()); } if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { return operation.parseResponse( null, this.getResult().getStatusCode(), request.getHeaderField(TableConstants.HeaderConstants.ETAG), opContext); } else { throw TableServiceException.generateTableServiceException( true, this.getResult(), operation, request.getErrorStream()); } } }; return ExecutionEngine.executeWithRetry( client, this, impl, options.getRetryPolicyFactory(), opContext); }
private void doQueryEscapeTest(String data) throws StorageException { TableRequestOptions options = new TableRequestOptions(); options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata); doQueryEscapeTestHelper(data, options); options.setTablePayloadFormat(TablePayloadFormat.Json); doQueryEscapeTestHelper(data, options); options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); doQueryEscapeTestHelper(data, options); options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); options.setPropertyResolver(new Class1()); doQueryEscapeTestHelper(data, options); }
private void doEscapeTest(String data, boolean useBatch, boolean includeInKey) throws StorageException { TableRequestOptions options = new TableRequestOptions(); options.setTablePayloadFormat(TablePayloadFormat.JsonFullMetadata); doEscapeTestHelper(data, useBatch, includeInKey, options); options.setTablePayloadFormat(TablePayloadFormat.Json); doEscapeTestHelper(data, useBatch, includeInKey, options); options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); doEscapeTestHelper(data, useBatch, includeInKey, options); options.setTablePayloadFormat(TablePayloadFormat.JsonNoMetadata); options.setPropertyResolver(new Class1()); doEscapeTestHelper(data, useBatch, includeInKey, options); }
/** * Reserved for internal use. Performs an insert operation on the specified table, using the * specified {@link TableRequestOptions} and {@link OperationContext}. * * <p>This method will invoke the Insert Entity REST API to execute this table 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, * storage account credentials, and any additional query parameters. * @param tableName A <code>String</code> 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 A {@link TableResult} containing the results of executing the operation. * @throws StorageException if an error occurs in the storage operation. */ private TableResult performInsert( final CloudTableClient client, final String tableName, final TableRequestOptions options, final OperationContext opContext) throws StorageException { final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName); final String tableIdentity = isTableEntry ? this.getEntity() .writeEntity(opContext) .get(TableConstants.TABLE_NAME) .getValueAsString() : null; // Upserts need row key and partition key if (!isTableEntry && this.opType != TableOperationType.INSERT) { Utility.assertNotNullOrEmpty( "Upserts require a valid PartitionKey", this.getEntity().getPartitionKey()); Utility.assertNotNullOrEmpty("Upserts require a valid RowKey", this.getEntity().getRowKey()); } final StorageOperation<CloudTableClient, TableOperation, TableResult> impl = new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) { @Override public TableResult execute( final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.insert( client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), operation.opType != TableOperationType.INSERT ? operation.getEntity().getEtag() : null, operation.opType.getUpdateType(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); AtomPubParser.writeSingleEntityToStream( operation.getEntity(), isTableEntry, request.getOutputStream(), opContext); this.setResult(ExecutionEngine.processRequest(request, opContext)); if (operation.opType == TableOperationType.INSERT) { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { throw TableServiceException.generateTableServiceException( false, this.getResult(), operation, request.getErrorStream()); } if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { throw TableServiceException.generateTableServiceException( true, this.getResult(), operation, request.getErrorStream()); } InputStream inStream = request.getInputStream(); TableResult res = null; try { final XMLStreamReader xmlr = Utility.createXMLStreamReaderFromStream(inStream); res = operation.parseResponse( xmlr, this.getResult().getStatusCode(), null, opContext); } finally { inStream.close(); } return res; } else { if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { return operation.parseResponse( null, this.getResult().getStatusCode(), request.getHeaderField(TableConstants.HeaderConstants.ETAG), opContext); } else { throw TableServiceException.generateTableServiceException( true, this.getResult(), operation, request.getErrorStream()); } } } }; return ExecutionEngine.executeWithRetry( client, this, impl, options.getRetryPolicyFactory(), opContext); }
/** * Reserved for internal use. Performs a delete operation on the specified table, using the * specified {@link TableRequestOptions} and {@link OperationContext}. * * <p>This method will invoke the <a * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135727.aspx">Delete Entity</a> * REST API to execute this table 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, * storage account credentials, and any additional query parameters. * @param tableName A <code>String</code> 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 A {@link TableResult} containing the results of executing the operation. * @throws StorageException if an error occurs in the storage operation. */ private TableResult performDelete( final CloudTableClient client, final String tableName, final TableRequestOptions options, final OperationContext opContext) throws StorageException { final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName); final String tableIdentity = isTableEntry ? this.getEntity() .writeEntity(opContext) .get(TableConstants.TABLE_NAME) .getValueAsString() : null; if (!isTableEntry) { Utility.assertNotNullOrEmpty("Delete requires a valid ETag", this.getEntity().getEtag()); Utility.assertNotNullOrEmpty( "Delete requires a valid PartitionKey", this.getEntity().getPartitionKey()); Utility.assertNotNullOrEmpty("Delete requires a valid RowKey", this.getEntity().getRowKey()); } final StorageOperation<CloudTableClient, TableOperation, TableResult> impl = new StorageOperation<CloudTableClient, TableOperation, TableResult>(options) { @Override public TableResult execute( final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.delete( client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); this.setResult(ExecutionEngine.processRequest(request, opContext)); if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND || this.getResult().getStatusCode() == HttpURLConnection.HTTP_CONFLICT) { throw TableServiceException.generateTableServiceException( false, this.getResult(), operation, request.getErrorStream()); } if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { throw TableServiceException.generateTableServiceException( true, this.getResult(), operation, request.getErrorStream()); } return operation.parseResponse(null, this.getResult().getStatusCode(), null, opContext); } }; return ExecutionEngine.executeWithRetry( client, this, impl, options.getRetryPolicyFactory(), opContext); }
/** * Reserved for internal use. Parses the operation response as a collection of entities. Reads * entity data from the specified input stream using the specified class type and optionally * projects each entity result with the specified resolver into an {@link ODataPayload} containing * a collection of {@link TableResult} objects. * * @param inStream The <code>InputStream</code> to read the data to parse from. * @param clazzType The class type <code>T</code> implementing {@link TableEntity} for the * entities returned. Set to <code>null</code> to ignore the returned entities and copy only * response properties into the {@link TableResult} objects. * @param resolver An {@link EntityResolver} instance to project the entities into instances of * type <code>R</code>. Set to <code>null</code> to return the entities as instances of the * class type <code>T</code>. * @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 used to track the execution of the * operation. * @return An {@link ODataPayload} containing a collection of {@link TableResult} objects with the * parsed operation response. * @throws InstantiationException if an error occurs while constructing the result. * @throws IllegalAccessException if an error occurs in reflection while parsing the result. * @throws StorageException if a storage service error occurs. * @throws IOException if an error occurs while accessing the stream. * @throws JsonParseException if an error occurs while parsing the stream. */ @SuppressWarnings("unchecked") static <T extends TableEntity, R> ODataPayload<?> parseQueryResponse( final InputStream inStream, final TableRequestOptions options, final Class<T> clazzType, final EntityResolver<R> resolver, final OperationContext opContext) throws JsonParseException, IOException, InstantiationException, IllegalAccessException, StorageException { ODataPayload<T> corePayload = null; ODataPayload<R> resolvedPayload = null; ODataPayload<?> commonPayload = null; JsonParser parser = createJsonParserFromStream(inStream); try { if (resolver != null) { resolvedPayload = new ODataPayload<R>(); commonPayload = resolvedPayload; } else { corePayload = new ODataPayload<T>(); commonPayload = corePayload; } if (!parser.hasCurrentToken()) { parser.nextToken(); } JsonUtilities.assertIsStartObjectJsonToken(parser); // move into data parser.nextToken(); // if there is a clazz type and if JsonNoMetadata, create a classProperties dictionary to use // for type inference once // instead of querying the cache many times HashMap<String, PropertyPair> classProperties = null; if (options.getTablePayloadFormat() == TablePayloadFormat.JsonNoMetadata && clazzType != null) { classProperties = PropertyPair.generatePropertyPairs(clazzType); } while (parser.getCurrentToken() != null) { if (parser.getCurrentToken() == JsonToken.FIELD_NAME && parser.getCurrentName().equals(ODataConstants.VALUE)) { // move to start of array parser.nextToken(); JsonUtilities.assertIsStartArrayJsonToken(parser); // go to properties parser.nextToken(); while (parser.getCurrentToken() == JsonToken.START_OBJECT) { final TableResult res = parseJsonEntity(parser, clazzType, classProperties, resolver, options, opContext); if (corePayload != null) { corePayload.tableResults.add(res); } if (resolver != null) { resolvedPayload.results.add((R) res.getResult()); } else { corePayload.results.add((T) res.getResult()); } parser.nextToken(); } JsonUtilities.assertIsEndArrayJsonToken(parser); } parser.nextToken(); } } finally { parser.close(); } return commonPayload; }
/** * Reserved for internal use. Parses the operation response as an entity. Parses the result * returned in the specified stream in JSON format into a {@link TableResult} containing an entity * of the specified class type projected using the specified resolver. * * @param parser The <code>JsonParser</code> to read the data to parse from. * @param clazzType The class type <code>T</code> implementing {@link TableEntity} for the entity * returned. Set to <code>null</code> to ignore the returned entity and copy only response * properties into the {@link TableResult} object. * @param resolver An {@link EntityResolver} instance to project the entity into an instance of * type <code>R</code>. Set to <code>null</code> to return the entity as an instance of the * class type <code>T</code>. * @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 used to track the execution of the * operation. * @return A {@link TableResult} containing the parsed entity result of the operation. * @throws IOException if an error occurs while accessing the stream. * @throws InstantiationException if an error occurs while constructing the result. * @throws IllegalAccessException if an error occurs in reflection while parsing the result. * @throws StorageException if a storage service error occurs. * @throws IOException if an error occurs while accessing the stream. * @throws JsonParseException if an error occurs while parsing the stream. */ private static <T extends TableEntity, R> TableResult parseJsonEntity( final JsonParser parser, final Class<T> clazzType, HashMap<String, PropertyPair> classProperties, final EntityResolver<R> resolver, final TableRequestOptions options, final OperationContext opContext) throws JsonParseException, IOException, StorageException, InstantiationException, IllegalAccessException { final TableResult res = new TableResult(); final HashMap<String, EntityProperty> properties = new HashMap<String, EntityProperty>(); if (!parser.hasCurrentToken()) { parser.nextToken(); } JsonUtilities.assertIsStartObjectJsonToken(parser); parser.nextToken(); // get all metadata, if present while (parser.getCurrentName().startsWith(ODataConstants.ODATA_PREFIX)) { final String name = parser.getCurrentName().substring(ODataConstants.ODATA_PREFIX.length()); // get the value token parser.nextToken(); if (name.equals(ODataConstants.ETAG)) { String etag = parser.getValueAsString(); res.setEtag(etag); } // get the key token parser.nextToken(); } if (resolver == null && clazzType == null) { return res; } // get object properties while (parser.getCurrentToken() != JsonToken.END_OBJECT) { String key = Constants.EMPTY_STRING; String val = Constants.EMPTY_STRING; EdmType edmType = null; // checks if this property is preceded by an OData property type annotation if (options.getTablePayloadFormat() != TablePayloadFormat.JsonNoMetadata && parser.getCurrentName().endsWith(ODataConstants.ODATA_TYPE_SUFFIX)) { parser.nextToken(); edmType = EdmType.parse(parser.getValueAsString()); parser.nextValue(); key = parser.getCurrentName(); val = parser.getValueAsString(); } else { key = parser.getCurrentName(); parser.nextToken(); val = parser.getValueAsString(); edmType = evaluateEdmType(parser.getCurrentToken(), parser.getValueAsString()); } final EntityProperty newProp = new EntityProperty(val, edmType); newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(key, newProp); parser.nextToken(); } String partitionKey = null; String rowKey = null; Date timestamp = null; String etag = null; // Remove core properties from map and set individually EntityProperty tempProp = properties.remove(TableConstants.PARTITION_KEY); if (tempProp != null) { partitionKey = tempProp.getValueAsString(); } tempProp = properties.remove(TableConstants.ROW_KEY); if (tempProp != null) { rowKey = tempProp.getValueAsString(); } tempProp = properties.remove(TableConstants.TIMESTAMP); if (tempProp != null) { tempProp.setDateBackwardCompatibility(false); timestamp = tempProp.getValueAsDate(); if (res.getEtag() == null) { etag = getETagFromTimestamp(tempProp.getValueAsString()); res.setEtag(etag); } } // do further processing for type if JsonNoMetdata by inferring type information via resolver or // clazzType if (options.getTablePayloadFormat() == TablePayloadFormat.JsonNoMetadata && (options.getPropertyResolver() != null || clazzType != null)) { if (options.getPropertyResolver() != null) { for (final Entry<String, EntityProperty> p : properties.entrySet()) { final String key = p.getKey(); final String value = p.getValue().getValueAsString(); EdmType edmType; // try to use the property resolver to get the type try { edmType = options.getPropertyResolver().propertyResolver(partitionKey, rowKey, key, value); } catch (Exception e) { throw new StorageException( StorageErrorCodeStrings.INTERNAL_ERROR, SR.CUSTOM_RESOLVER_THREW, Constants.HeaderConstants.HTTP_UNUSED_306, null, e); } // try to create a new entity property using the returned type try { final EntityProperty newProp = new EntityProperty(value, edmType); newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(p.getKey(), newProp); } catch (IllegalArgumentException e) { throw new StorageException( StorageErrorCodeStrings.INVALID_TYPE, String.format(SR.FAILED_TO_PARSE_PROPERTY, key, value, edmType), Constants.HeaderConstants.HTTP_UNUSED_306, null, e); } } } else if (clazzType != null) { if (classProperties == null) { classProperties = PropertyPair.generatePropertyPairs(clazzType); } for (final Entry<String, EntityProperty> p : properties.entrySet()) { PropertyPair propPair = classProperties.get(p.getKey()); if (propPair != null) { final EntityProperty newProp = new EntityProperty(p.getValue().getValueAsString(), propPair.type); newProp.setDateBackwardCompatibility(options.getDateBackwardCompatibility()); properties.put(p.getKey(), newProp); } } } } // set the result properties, now that they are appropriately parsed res.setProperties(properties); // use resolver if provided, else create entity based on clazz type if (resolver != null) { res.setResult( resolver.resolve(partitionKey, rowKey, timestamp, res.getProperties(), res.getEtag())); } else if (clazzType != null) { // Generate new entity and return final T entity = clazzType.newInstance(); entity.setEtag(res.getEtag()); entity.setPartitionKey(partitionKey); entity.setRowKey(rowKey); entity.setTimestamp(timestamp); entity.readEntity(res.getProperties(), opContext); res.setResult(entity); } return res; }