From 6a25ef7d48334d8a891d54b30ae9f086e42737ac Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 7 Dec 2011 10:34:55 -0800 Subject: [PATCH 01/76] Initial implementation of table service --- .../windowsazure/services/table/Exports.java | 32 ++++ .../services/table/TableConfiguration.java | 21 +++ .../services/table/TableContract.java | 31 ++++ .../services/table/TableService.java | 38 +++++ .../table/implementation/SharedKeyFilter.java | 26 +++ .../implementation/SharedKeyLiteFilter.java | 26 +++ .../TableExceptionProcessor.java | 107 ++++++++++++ .../table/implementation/TableRestProxy.java | 141 +++++++++++++++ .../models/GetServicePropertiesResult.java | 27 +++ .../table/models/ServiceProperties.java | 161 ++++++++++++++++++ .../table/models/TableServiceOptions.java | 29 ++++ 11 files changed, 639 insertions(+) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java new file mode 100644 index 0000000000000..484cd066f2a09 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -0,0 +1,32 @@ +/** + * Copyright 2011 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; + +import com.microsoft.windowsazure.services.core.Builder; +import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter; +import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter; +import com.microsoft.windowsazure.services.table.implementation.TableExceptionProcessor; +import com.microsoft.windowsazure.services.table.implementation.TableRestProxy; + +public class Exports implements Builder.Exports { + @Override + public void register(Builder.Registry registry) { + registry.add(TableContract.class, TableExceptionProcessor.class); + registry.add(TableExceptionProcessor.class); + registry.add(TableRestProxy.class); + registry.add(SharedKeyLiteFilter.class); + registry.add(SharedKeyFilter.class); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java new file mode 100644 index 0000000000000..49c90e0ddf3d2 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java @@ -0,0 +1,21 @@ +/** + * Copyright 2011 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; + +public class TableConfiguration { + public final static String ACCOUNT_NAME = "table.accountName"; + public final static String ACCOUNT_KEY = "table.accountKey"; + public final static String URI = "table.uri"; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java new file mode 100644 index 0000000000000..9a849047abc04 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -0,0 +1,31 @@ +/** + * Copyright 2011 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; + +import com.microsoft.windowsazure.services.core.FilterableService; +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.ServiceProperties; +import com.microsoft.windowsazure.services.table.models.TableServiceOptions; + +public interface TableContract extends FilterableService { + GetServicePropertiesResult getServiceProperties() throws ServiceException; + + GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException; + + void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException; + + void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java new file mode 100644 index 0000000000000..2e732e4fc1d6e --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java @@ -0,0 +1,38 @@ +/** + * Copyright 2011 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; + +import com.microsoft.windowsazure.services.core.Configuration; + +public class TableService { + private TableService() { + } + + public static TableContract create() { + return create(null, Configuration.getInstance()); + } + + public static TableContract create(Configuration config) { + return create(null, config); + } + + public static TableContract create(String profile) { + return create(profile, Configuration.getInstance()); + } + + public static TableContract create(String profile, Configuration config) { + return config.create(profile, TableContract.class); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java new file mode 100644 index 0000000000000..9cfbe21f43ff2 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -0,0 +1,26 @@ +/** + * Copyright 2011 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.implementation; + +import javax.inject.Named; + +import com.microsoft.windowsazure.services.table.TableConfiguration; + +public class SharedKeyFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter { + public SharedKeyFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName, + @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) { + super(accountName, accountKey); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java new file mode 100644 index 0000000000000..094fe429145a7 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java @@ -0,0 +1,26 @@ +/** + * Copyright 2011 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.implementation; + +import javax.inject.Named; + +import com.microsoft.windowsazure.services.table.TableConfiguration; + +public class SharedKeyLiteFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter { + public SharedKeyLiteFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName, + @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) { + super(accountName, accountKey); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java new file mode 100644 index 0000000000000..6aadc0650f3c3 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -0,0 +1,107 @@ +/** + * Copyright 2011 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.implementation; + +import javax.inject.Inject; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; +import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.ServiceProperties; +import com.microsoft.windowsazure.services.table.models.TableServiceOptions; +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.UniformInterfaceException; + +public class TableExceptionProcessor implements TableContract { + private static Log log = LogFactory.getLog(TableExceptionProcessor.class); + private final TableContract service; + + @Inject + public TableExceptionProcessor(TableRestProxy service) { + this.service = service; + } + + public TableExceptionProcessor(TableContract service) { + this.service = service; + } + + @Override + public TableContract withFilter(ServiceFilter filter) { + return new TableExceptionProcessor(service.withFilter(filter)); + } + + private ServiceException processCatch(ServiceException e) { + log.warn(e.getMessage(), e.getCause()); + return ServiceExceptionFactory.process("blob", e); + } + + @Override + public GetServicePropertiesResult getServiceProperties() throws ServiceException { + try { + return service.getServiceProperties(); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException { + try { + return service.getServiceProperties(options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException { + try { + service.setServiceProperties(serviceProperties); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) + throws ServiceException { + try { + service.setServiceProperties(serviceProperties, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java new file mode 100644 index 0000000000000..5e965269030ab --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -0,0 +1,141 @@ +/** + * Copyright 2011 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.implementation; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Named; + +import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter; +import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient; +import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; +import com.microsoft.windowsazure.services.table.TableConfiguration; +import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.ServiceProperties; +import com.microsoft.windowsazure.services.table.models.TableServiceOptions; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + +public class TableRestProxy implements TableContract { + private static final String API_VERSION = "2011-08-18"; + private final HttpURLConnectionClient channel; + private final String accountName; + private final String url; + private final RFC1123DateConverter dateMapper; + private final ServiceFilter[] filters; + private final SharedKeyFilter filter; + + @Inject + public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.ACCOUNT_NAME) String accountName, + @Named(TableConfiguration.URI) String url, SharedKeyFilter filter) { + + this.channel = channel; + this.accountName = accountName; + this.url = url; + this.filter = filter; + this.dateMapper = new RFC1123DateConverter(); + this.filters = new ServiceFilter[0]; + channel.addFilter(filter); + } + + public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String accountName, String url, + SharedKeyFilter filter, RFC1123DateConverter dateMapper) { + + this.channel = channel; + this.filters = filters; + this.accountName = accountName; + this.url = url; + this.filter = filter; + this.dateMapper = dateMapper; + } + + @Override + public TableContract withFilter(ServiceFilter filter) { + ServiceFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1); + newFilters[filters.length] = filter; + return new TableRestProxy(this.channel, newFilters, this.accountName, this.url, this.filter, this.dateMapper); + } + + private void ThrowIfError(ClientResponse r) { + PipelineHelpers.ThrowIfError(r); + } + + private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { + return PipelineHelpers.addOptionalQueryParam(webResource, key, value); + } + + private WebResource addOptionalQueryParam(WebResource webResource, String key, int value, int defaultValue) { + return PipelineHelpers.addOptionalQueryParam(webResource, key, value, defaultValue); + } + + private Builder addOptionalMetadataHeader(Builder builder, Map metadata) { + return PipelineHelpers.addOptionalMetadataHeader(builder, metadata); + } + + private HashMap getMetadataFromHeaders(ClientResponse response) { + return PipelineHelpers.getMetadataFromHeaders(response); + } + + private WebResource getResource(TableServiceOptions options) { + WebResource webResource = channel.resource(url).path("/"); + webResource = addOptionalQueryParam(webResource, "timeout", options.getTimeout()); + for (ServiceFilter filter : filters) { + webResource.addFilter(new ClientFilterAdapter(filter)); + } + + return webResource; + } + + @Override + public GetServicePropertiesResult getServiceProperties() throws ServiceException { + return getServiceProperties(new TableServiceOptions()); + } + + @Override + public GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("/").queryParam("resType", "service") + .queryParam("comp", "properties"); + + WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); + + GetServicePropertiesResult result = new GetServicePropertiesResult(); + result.setValue(builder.get(ServiceProperties.class)); + return result; + } + + @Override + public void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException { + setServiceProperties(serviceProperties, new TableServiceOptions()); + } + + @Override + public void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) + throws ServiceException { + WebResource webResource = getResource(options).path("/").queryParam("resType", "service") + .queryParam("comp", "properties"); + + WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); + + builder.put(serviceProperties); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java new file mode 100644 index 0000000000000..30ab7196d4489 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java @@ -0,0 +1,27 @@ +/** + * Copyright 2011 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.models; + +public class GetServicePropertiesResult { + private ServiceProperties value; + + public ServiceProperties getValue() { + return value; + } + + public void setValue(ServiceProperties value) { + this.value = value; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java new file mode 100644 index 0000000000000..2e068e4fe9dff --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java @@ -0,0 +1,161 @@ +/** + * Copyright 2011 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.models; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "StorageServiceProperties") +public class ServiceProperties { + private Logging logging = new Logging(); + private Metrics metrics = new Metrics(); + + @XmlElement(name = "Logging") + public Logging getLogging() { + return logging; + } + + public void setLogging(Logging logging) { + this.logging = logging; + } + + @XmlElement(name = "Metrics") + public Metrics getMetrics() { + return metrics; + } + + public void setMetrics(Metrics metrics) { + this.metrics = metrics; + } + + public static class Logging { + private String version; + private Boolean delete; + private Boolean read; + private Boolean write; + private RetentionPolicy retentionPolicy; + + @XmlElement(name = "RetentionPolicy") + public RetentionPolicy getRetentionPolicy() { + return retentionPolicy; + } + + public void setRetentionPolicy(RetentionPolicy retentionPolicy) { + this.retentionPolicy = retentionPolicy; + } + + @XmlElement(name = "Write") + public boolean isWrite() { + return write; + } + + public void setWrite(boolean write) { + this.write = write; + } + + @XmlElement(name = "Read") + public boolean isRead() { + return read; + } + + public void setRead(boolean read) { + this.read = read; + } + + @XmlElement(name = "Delete") + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + } + + @XmlElement(name = "Version") + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + public static class Metrics { + private String version; + private boolean enabled; + private Boolean includeAPIs; + private RetentionPolicy retentionPolicy; + + @XmlElement(name = "RetentionPolicy") + public RetentionPolicy getRetentionPolicy() { + return retentionPolicy; + } + + public void setRetentionPolicy(RetentionPolicy retentionPolicy) { + this.retentionPolicy = retentionPolicy; + } + + @XmlElement(name = "IncludeAPIs") + public Boolean isIncludeAPIs() { + return includeAPIs; + } + + public void setIncludeAPIs(Boolean includeAPIs) { + this.includeAPIs = includeAPIs; + } + + @XmlElement(name = "Enabled") + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @XmlElement(name = "Version") + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + public static class RetentionPolicy { + private boolean enabled; + private Integer days; // nullable, because optional if "enabled" is false + + @XmlElement(name = "Days") + public Integer getDays() { + return days; + } + + public void setDays(Integer days) { + this.days = days; + } + + @XmlElement(name = "Enabled") + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java new file mode 100644 index 0000000000000..e42240885c15b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java @@ -0,0 +1,29 @@ +/** + * Copyright 2011 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.models; + +public class TableServiceOptions { + // Nullable because it is optional + private Integer timeout; + + public Integer getTimeout() { + return timeout; + } + + public TableServiceOptions setTimeout(Integer timeout) { + this.timeout = timeout; + return this; + } +} From cfb7c936128d18c13167038def909b6aae7f149a Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 7 Dec 2011 16:08:14 -0800 Subject: [PATCH 02/76] Integration tests --- .../blob/implementation/SharedKeyFilter.java | 35 ++++--- .../implementation/SharedKeyLiteFilter.java | 17 ++-- .../services/core/utils/pipeline/Exports.java | 23 +++-- .../services/table/TableContract.java | 6 ++ .../table/implementation/SharedKeyFilter.java | 63 +++++++++++++ .../TableExceptionProcessor.java | 28 ++++++ .../table/implementation/TableRestProxy.java | 22 +++++ .../table/models/QueryTablesOptions.java | 20 ++++ .../table/models/QueryTablesResult.java | 14 +++ ...windowsazure.services.core.Builder$Exports | 1 + .../services/table/IntegrationTestBase.java | 43 +++++++++ .../table/TableServiceIntegrationTest.java | 91 +++++++++++++++++++ .../com.microsoft.windowsazure.properties | 5 +- 13 files changed, 337 insertions(+), 31 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java index 6481932c7cde0..9cbc9ae72bf22 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.implementation; @@ -44,6 +44,18 @@ public SharedKeyFilter(@Named(BlobConfiguration.ACCOUNT_NAME) String accountName this.signer = new HmacSHA256Sign(accountKey); } + protected String getHeader(ClientRequest cr, String headerKey) { + return SharedKeyUtils.getHeader(cr, headerKey); + } + + protected HmacSHA256Sign getSigner() { + return signer; + } + + protected String getAccountName() { + return accountName; + } + @Override public ClientResponse handle(ClientRequest cr) throws ClientHandlerException { // Only sign if no other filter is handling authorization @@ -60,6 +72,7 @@ public ClientResponse handle(ClientRequest cr) throws ClientHandlerException { return this.getNext().handle(cr); } + @Override public void onBeforeStreamingEntity(ClientRequest clientRequest) { // All headers should be known at this point, time to sign! sign(clientRequest); @@ -105,7 +118,7 @@ public void sign(ClientRequest cr) { cr.getHeaders().putSingle("Authorization", "SharedKey " + this.accountName + ":" + signature); } - private void addOptionalDateHeader(ClientRequest cr) { + protected void addOptionalDateHeader(ClientRequest cr) { String date = getHeader(cr, "Date"); if (date == "") { date = new RFC1123DateConverter().format(new Date()); @@ -209,8 +222,4 @@ private String getCanonicalizedResource(ClientRequest cr) { return result; } - - private String getHeader(ClientRequest cr, String headerKey) { - return SharedKeyUtils.getHeader(cr, headerKey); - } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java index 0134195d5c3aa..37ff1321d5d96 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.implementation; @@ -59,6 +59,7 @@ public ClientResponse handle(ClientRequest cr) throws ClientHandlerException { return this.getNext().handle(cr); } + @Override public void onBeforeStreamingEntity(ClientRequest clientRequest) { // All headers should be known at this point, time to sign! sign(clientRequest); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java index a562b7c2122e7..f7aae2cd9188f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.core.utils.pipeline; @@ -22,11 +22,14 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.client.filter.LoggingFilter; public class Exports implements Builder.Exports { + @Override public void register(Registry registry) { registry.add(new Builder.Factory() { + @Override public ClientConfig create(String profile, Builder builder, Map properties) { ClientConfig clientConfig = new DefaultClientConfig(); for (Entry entry : properties.entrySet()) { @@ -37,6 +40,7 @@ public ClientConfig create(String profile, Builder builder, Map }); registry.add(new Builder.Factory() { + @Override public Client create(String profile, Builder builder, Map properties) { ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig"); Client client = Client.create(clientConfig); @@ -45,10 +49,11 @@ public Client create(String profile, Builder builder, Map proper }); registry.add(new Builder.Factory() { + @Override public HttpURLConnectionClient create(String profile, Builder builder, Map properties) { ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig"); HttpURLConnectionClient client = HttpURLConnectionClient.create(clientConfig); - //client.addFilter(new LoggingFilter()); + client.addFilter(new LoggingFilter()); return client; } }); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 9a849047abc04..6e759d01d4334 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -17,6 +17,8 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; @@ -28,4 +30,8 @@ public interface TableContract extends FilterableService { void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException; void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException; + + QueryTablesResult queryTables() throws ServiceException; + + QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java index 9cfbe21f43ff2..ffa302d156405 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -14,13 +14,76 @@ */ package com.microsoft.windowsazure.services.table.implementation; +import java.util.List; + import javax.inject.Named; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.microsoft.windowsazure.services.blob.implementation.SharedKeyUtils; +import com.microsoft.windowsazure.services.blob.implementation.SharedKeyUtils.QueryParam; import com.microsoft.windowsazure.services.table.TableConfiguration; +import com.sun.jersey.api.client.ClientRequest; public class SharedKeyFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter { + private static Log log = LogFactory.getLog(SharedKeyFilter.class); + public SharedKeyFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName, @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) { super(accountName, accountKey); } + + /* + * StringToSign = VERB + "\n" + + * Content-MD5 + "\n" + + * Content-Type + "\n" + + * Date + "\n" + + * CanonicalizedResource; + */ + @Override + public void sign(ClientRequest cr) { + // gather signed material + addOptionalDateHeader(cr); + + // build signed string + String stringToSign = cr.getMethod() + "\n" + getHeader(cr, "Content-MD5") + "\n" + + getHeader(cr, "Content-Type") + "\n" + getHeader(cr, "Date") + "\n"; + + stringToSign += getCanonicalizedResource(cr); + + if (log.isDebugEnabled()) { + log.debug(String.format("String to sign: \"%s\"", stringToSign)); + } + System.out.println(String.format("String to sign: \"%s\"", stringToSign)); + + String signature = this.getSigner().sign(stringToSign); + cr.getHeaders().putSingle("Authorization", "SharedKey " + this.getAccountName() + ":" + signature); + } + + /** + * This format supports Shared Key and Shared Key Lite for all versions of the Table service, and Shared Key Lite + * for the 2009-09-19 version of the Blob and Queue services. This format is identical to that used with previous + * versions of the storage services. Construct the CanonicalizedResource string in this format as follows: + * + * 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of the account that owns + * the resource being accessed. + * + * 2. Append the resource's encoded URI path. 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. + */ + private String getCanonicalizedResource(ClientRequest cr) { + String result = "/" + this.getAccountName(); + + result += cr.getURI().getPath(); + + List queryParams = SharedKeyUtils.getQueryParams(cr.getURI().getQuery()); + for (QueryParam p : queryParams) { + if ("comp".equals(p.getName())) { + result += "?" + p.getName() + "=" + p.getValues().get(0); + } + } + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 6aadc0650f3c3..a771f2e57c70c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -24,6 +24,8 @@ import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.sun.jersey.api.client.ClientHandlerException; @@ -104,4 +106,30 @@ public void setServiceProperties(ServiceProperties serviceProperties, TableServi throw processCatch(new ServiceException(e)); } } + + @Override + public QueryTablesResult queryTables() throws ServiceException { + try { + return service.queryTables(); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { + try { + return service.queryTables(options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 5e965269030ab..9e1fdd24e27b8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -30,6 +30,8 @@ import com.microsoft.windowsazure.services.table.TableConfiguration; import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.sun.jersey.api.client.ClientResponse; @@ -138,4 +140,24 @@ public void setServiceProperties(ServiceProperties serviceProperties, TableServi builder.put(serviceProperties); } + + @Override + public QueryTablesResult queryTables() throws ServiceException { + return queryTables(new QueryTablesOptions()); + } + + @Override + public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("Tables" + options.getQuery()); + + WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); + + ClientResponse response = builder.get(ClientResponse.class); + ThrowIfError(response); + + QueryTablesResult result = new QueryTablesResult(); + result.setContinuationToken(response.getHeaders().getFirst("x-ms-continuation-NextTableName")); + + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java new file mode 100644 index 0000000000000..e60e10bfe4ef5 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -0,0 +1,20 @@ +package com.microsoft.windowsazure.services.table.models; + +public class QueryTablesOptions extends TableServiceOptions { + private String query; + + @Override + public QueryTablesOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public String getQuery() { + return query; + } + + public QueryTablesOptions setQuery(String query) { + this.query = query; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java new file mode 100644 index 0000000000000..0215444e944bb --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class QueryTablesResult { + private String continuationToken; + + public String getContinuationToken() { + return continuationToken; + } + + public void setContinuationToken(String continuationToken) { + this.continuationToken = continuationToken; + } + +} diff --git a/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports b/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports index cc7087393981f..07fdb1c4e7b67 100644 --- a/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports +++ b/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports @@ -1,5 +1,6 @@ com.microsoft.windowsazure.services.blob.Exports com.microsoft.windowsazure.services.queue.Exports +com.microsoft.windowsazure.services.table.Exports com.microsoft.windowsazure.services.serviceBus.Exports com.microsoft.windowsazure.services.serviceBus.implementation.Exports com.microsoft.windowsazure.services.core.utils.Exports diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java new file mode 100644 index 0000000000000..8b7b18382d59d --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java @@ -0,0 +1,43 @@ +/** + * Copyright 2011 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; + +import com.microsoft.windowsazure.services.core.Configuration; + +public abstract class IntegrationTestBase { + protected static Configuration createConfiguration() { + Configuration config = Configuration.getInstance(); + overrideWithEnv(config, TableConfiguration.ACCOUNT_NAME); + overrideWithEnv(config, TableConfiguration.ACCOUNT_KEY); + overrideWithEnv(config, TableConfiguration.URI); + return config; + } + + private static void overrideWithEnv(Configuration config, String key) { + String value = System.getenv(key); + if (value == null) + return; + + config.setProperty(key, value); + } + + protected static boolean isRunningWithEmulator(Configuration config) { + String accountName = "devstoreaccount1"; + String accountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + return accountName.equals(config.getProperty(TableConfiguration.ACCOUNT_NAME)) + && accountKey.equals(config.getProperty(TableConfiguration.ACCOUNT_KEY)); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java new file mode 100644 index 0000000000000..2c67bbce8c0ea --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 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; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.table.models.QueryTablesResult; +import com.microsoft.windowsazure.services.table.models.ServiceProperties; + +public class TableServiceIntegrationTest extends IntegrationTestBase { + + @Test + public void getServicePropertiesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Don't run this test with emulator, as v1.6 doesn't support this method + if (isRunningWithEmulator(config)) { + return; + } + + // Act + ServiceProperties props = service.getServiceProperties().getValue(); + + // Assert + assertNotNull(props); + assertNotNull(props.getLogging()); + assertNotNull(props.getLogging().getRetentionPolicy()); + assertNotNull(props.getLogging().getVersion()); + assertNotNull(props.getMetrics().getRetentionPolicy()); + assertNotNull(props.getMetrics().getVersion()); + } + + @Test + public void setServicePropertiesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Don't run this test with emulator, as v1.6 doesn't support this method + if (isRunningWithEmulator(config)) { + return; + } + + // Act + ServiceProperties props = service.getServiceProperties().getValue(); + + props.getLogging().setRead(true); + service.setServiceProperties(props); + + props = service.getServiceProperties().getValue(); + + // Assert + assertNotNull(props); + assertNotNull(props.getLogging()); + assertNotNull(props.getLogging().getRetentionPolicy()); + assertNotNull(props.getLogging().getVersion()); + assertTrue(props.getLogging().isRead()); + assertNotNull(props.getMetrics().getRetentionPolicy()); + assertNotNull(props.getMetrics().getVersion()); + } + + @Test + public void queryTablesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + QueryTablesResult result = service.queryTables(); + + // Assert + assertNotNull(result); + } +} diff --git a/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties b/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties index e7bf06b034de2..7739acd3e7333 100644 --- a/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties +++ b/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties @@ -7,4 +7,7 @@ blob.accountKey=%BLOB_ACCOUNTKEY% blob.uri=http://%BLOB_ACCOUNTNAME%.blob.core.windows.net queue.accountName=%QUEUE_ACCOUNTNAME% queue.accountKey=%QUEUE_ACCOUNTKEY% -queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net \ No newline at end of file +queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net +table.accountName=%TABLE_ACCOUNTNAME% +table.accountKey=%TABLE_ACCOUNTKEY% +table.uri=http://%TABLE_ACCOUNTNAME%.table.core.windows.net \ No newline at end of file From bbcee3ed5b242de5dc372f6cbe373509c38c6ff9 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Mon, 9 Jan 2012 12:50:45 -0800 Subject: [PATCH 03/76] Cosmetic changes --- .../table/implementation/SharedKeyFilter.java | 1 + .../table/implementation/TableRestProxy.java | 23 ++++++++----------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java index ffa302d156405..19a5cc737bc54 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -55,6 +55,7 @@ public void sign(ClientRequest cr) { if (log.isDebugEnabled()) { log.debug(String.format("String to sign: \"%s\"", stringToSign)); } + //TODO: Remove or comment the following line System.out.println(String.format("String to sign: \"%s\"", stringToSign)); String signature = this.getSigner().sign(stringToSign); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 9e1fdd24e27b8..52b3eb6bbdac5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -15,8 +15,6 @@ package com.microsoft.windowsazure.services.table.implementation; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import javax.inject.Inject; import javax.inject.Named; @@ -86,16 +84,8 @@ private WebResource addOptionalQueryParam(WebResource webResource, String key, O return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } - private WebResource addOptionalQueryParam(WebResource webResource, String key, int value, int defaultValue) { - return PipelineHelpers.addOptionalQueryParam(webResource, key, value, defaultValue); - } - - private Builder addOptionalMetadataHeader(Builder builder, Map metadata) { - return PipelineHelpers.addOptionalMetadataHeader(builder, metadata); - } - - private HashMap getMetadataFromHeaders(ClientResponse response) { - return PipelineHelpers.getMetadataFromHeaders(response); + private Builder addOptionalHeader(Builder builder, String name, Object value) { + return PipelineHelpers.addOptionalHeader(builder, name, value); } private WebResource getResource(TableServiceOptions options) { @@ -148,7 +138,7 @@ public QueryTablesResult queryTables() throws ServiceException { @Override public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { - WebResource webResource = getResource(options).path("Tables" + options.getQuery()); + WebResource webResource = getResource(options).path("Tables" + getTableQuery(options.getQuery())); WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); @@ -160,4 +150,11 @@ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceE return result; } + + private String getTableQuery(String query) { + if (query == null || query.length() == 0) + return ""; + + return "(" + query + ")"; + } } From 9432fac94bcf230779f2953ce323945337f0b6d9 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Tue, 10 Jan 2012 15:31:06 -0800 Subject: [PATCH 04/76] Implement table creation/deletion/querying --- .../windowsazure/services/blob/Exports.java | 18 +- .../blob/implementation/BlobRestProxy.java | 10 +- .../ContainerACLDateAdapter.java | 4 +- ...nverter.java => ISO8601DateConverter.java} | 2 +- .../blob/implementation/SharedKeyFilter.java | 2 +- .../core/utils/CommaStringBuilder.java | 47 ++++ .../HttpURLConnectionClientHandler.java | 4 +- .../core/utils/pipeline/PipelineHelpers.java | 23 -- .../windowsazure/services/table/Exports.java | 5 + .../services/table/TableContract.java | 18 ++ .../implementation/AtomReaderWriter.java | 143 ++++++++++++ .../DefaultXMLStreamFactory.java | 40 ++++ .../TableExceptionProcessor.java | 108 ++++++++- .../table/implementation/TableRestProxy.java | 213 ++++++++++++++++-- .../implementation/XMLStreamFactory.java | 13 ++ .../table/models/BinaryFilterExpression.java | 34 +++ .../models/ConstantFilterExpression.java | 14 ++ .../table/models/FilterExpression.java | 47 ++++ .../services/table/models/GetTableResult.java | 13 ++ .../table/models/ListTablesOptions.java | 20 ++ .../models/LitteralFilterExpression.java | 14 ++ .../services/table/models/QueryBuilder.java | 96 ++++++++ .../table/models/QueryTablesOptions.java | 6 +- .../table/models/QueryTablesResult.java | 10 + .../services/table/models/TableEntry.java | 13 ++ .../table/models/UnaryFilterExpression.java | 24 ++ .../services/table/AtomReaderWriterTests.java | 81 +++++++ .../table/TableServiceIntegrationTest.java | 173 ++++++++++++++ 28 files changed, 1135 insertions(+), 60 deletions(-) rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/{ContainerACLDateConverter.java => ISO8601DateConverter.java} (97%) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java index 197537a9db826..8d9d63bc8972d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java @@ -2,20 +2,21 @@ * Copyright 2011 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 + * 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. + * 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.blob; import com.microsoft.windowsazure.services.blob.implementation.BlobExceptionProcessor; import com.microsoft.windowsazure.services.blob.implementation.BlobRestProxy; +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter; import com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter; import com.microsoft.windowsazure.services.core.Builder; @@ -28,5 +29,6 @@ public void register(Builder.Registry registry) { registry.add(BlobRestProxy.class); registry.add(SharedKeyLiteFilter.class); registry.add(SharedKeyFilter.class); + registry.add(ISO8601DateConverter.class); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java index 3907ac20db407..5bba95ef4599c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java @@ -69,10 +69,10 @@ import com.microsoft.windowsazure.services.blob.models.SetContainerMetadataOptions; import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder; import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter; import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient; import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; -import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers.EnumCommaStringBuilder; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; @@ -155,18 +155,18 @@ private HashMap getMetadataFromHeaders(ClientResponse response) } private WebResource addOptionalBlobListingIncludeQueryParam(ListBlobsOptions options, WebResource webResource) { - EnumCommaStringBuilder sb = new EnumCommaStringBuilder(); + CommaStringBuilder sb = new CommaStringBuilder(); sb.addValue(options.isIncludeSnapshots(), "snapshots"); sb.addValue(options.isIncludeUncommittedBlobs(), "uncommittedblobs"); sb.addValue(options.isIncludeMetadata(), "metadata"); - webResource = addOptionalQueryParam(webResource, "include", sb.getValue()); + webResource = addOptionalQueryParam(webResource, "include", sb.toString()); return webResource; } private WebResource addOptionalContainerIncludeQueryParam(ListContainersOptions options, WebResource webResource) { - EnumCommaStringBuilder sb = new EnumCommaStringBuilder(); + CommaStringBuilder sb = new CommaStringBuilder(); sb.addValue(options.isIncludeMetadata(), "metadata"); - webResource = addOptionalQueryParam(webResource, "include", sb.getValue()); + webResource = addOptionalQueryParam(webResource, "include", sb.toString()); return webResource; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java index 39e67d931a384..af860e50936dd 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java @@ -25,11 +25,11 @@ public class ContainerACLDateAdapter extends XmlAdapter { @Override public Date unmarshal(String arg0) throws Exception { - return new ContainerACLDateConverter().parse(arg0); + return new ISO8601DateConverter().parse(arg0); } @Override public String marshal(Date arg0) throws Exception { - return new ContainerACLDateConverter().format(arg0); + return new ISO8601DateConverter().format(arg0); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java similarity index 97% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 209a345d26a0a..a5f02e0256372 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -24,7 +24,7 @@ /* * "not quite" ISO 8601 date time conversion routines */ -public class ContainerACLDateConverter { +public class ISO8601DateConverter { // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java index 9cbc9ae72bf22..9072ead435645 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java @@ -122,7 +122,7 @@ protected void addOptionalDateHeader(ClientRequest cr) { String date = getHeader(cr, "Date"); if (date == "") { date = new RFC1123DateConverter().format(new Date()); - cr.getHeaders().add("Date", date); + cr.getHeaders().putSingle("Date", date); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java new file mode 100644 index 0000000000000..3623028e5ef16 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java @@ -0,0 +1,47 @@ +package com.microsoft.windowsazure.services.core.utils; + +import java.util.List; + +public class CommaStringBuilder { + private final StringBuilder sb = new StringBuilder(); + + public void add(String representation) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(representation); + } + + public void addValue(boolean value, String representation) { + if (value) { + add(representation); + } + } + + public static String join(List values) { + CommaStringBuilder sb = new CommaStringBuilder(); + + for (String value : values) { + sb.add(value); + } + + return sb.toString(); + } + + public static String join(String... values) { + CommaStringBuilder sb = new CommaStringBuilder(); + + for (String value : values) { + sb.add(value); + } + + return sb.toString(); + } + + @Override + public String toString() { + if (sb.length() == 0) + return null; + return sb.toString(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java index 0610eb8d7bc94..20d9046c5358c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java @@ -27,7 +27,7 @@ import javax.ws.rs.core.MultivaluedMap; -import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers.EnumCommaStringBuilder; +import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.ClientRequest; import com.sun.jersey.api.client.ClientResponse; @@ -269,7 +269,7 @@ private void setURLConnectionHeaders(MultivaluedMap headers, Htt urlConnection.setRequestProperty(e.getKey(), ClientRequest.getHeaderValue(vs.get(0))); } else { - EnumCommaStringBuilder sb = new EnumCommaStringBuilder(); + CommaStringBuilder sb = new CommaStringBuilder(); for (Object v : e.getValue()) { sb.add(ClientRequest.getHeaderValue(v)); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java index 95fce9e9dbeb2..9cebe855ee2a6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java @@ -33,29 +33,6 @@ public static void ThrowIfError(ClientResponse r) { } } - public static class EnumCommaStringBuilder { - private final StringBuilder sb = new StringBuilder(); - - public void add(String representation) { - if (sb.length() > 0) { - sb.append(","); - } - sb.append(representation); - } - - public void addValue(boolean value, String representation) { - if (value) { - add(representation); - } - } - - public String getValue() { - if (sb.length() == 0) - return null; - return sb.toString(); - } - } - public static WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { if (value != null) { webResource = webResource.queryParam(key, value.toString()); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java index 484cd066f2a09..c36841928b064 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -15,10 +15,13 @@ package com.microsoft.windowsazure.services.table; import com.microsoft.windowsazure.services.core.Builder; +import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; +import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter; import com.microsoft.windowsazure.services.table.implementation.TableExceptionProcessor; import com.microsoft.windowsazure.services.table.implementation.TableRestProxy; +import com.microsoft.windowsazure.services.table.implementation.XMLStreamFactory; public class Exports implements Builder.Exports { @Override @@ -28,5 +31,7 @@ public void register(Builder.Registry registry) { registry.add(TableRestProxy.class); registry.add(SharedKeyLiteFilter.class); registry.add(SharedKeyFilter.class); + registry.add(XMLStreamFactory.class, DefaultXMLStreamFactory.class); + registry.add(AtomReaderWriter.class); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 6e759d01d4334..4d9776bde42cb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -17,6 +17,8 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -31,6 +33,22 @@ public interface TableContract extends FilterableService { void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException; + void createTable(String table) throws ServiceException; + + void createTable(String table, TableServiceOptions options) throws ServiceException; + + void deleteTable(String table) throws ServiceException; + + void deleteTable(String table, TableServiceOptions options) throws ServiceException; + + GetTableResult getTable(String table) throws ServiceException; + + GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException; + + QueryTablesResult listTables() throws ServiceException; + + QueryTablesResult listTables(ListTablesOptions options) throws ServiceException; + QueryTablesResult queryTables() throws ServiceException; QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java new file mode 100644 index 0000000000000..bd8439e5ee430 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -0,0 +1,143 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; +import com.microsoft.windowsazure.services.core.utils.DateFactory; +import com.microsoft.windowsazure.services.table.models.TableEntry; + +public class AtomReaderWriter { + private final XMLStreamFactory xmlStreamFactory; + private final DateFactory dateFactory; + private final ISO8601DateConverter iso8601DateConverter; + + @Inject + public AtomReaderWriter(XMLStreamFactory xmlStreamFactory, DateFactory dateFactory, + ISO8601DateConverter iso8601DateConverter) { + this.xmlStreamFactory = xmlStreamFactory; + this.dateFactory = dateFactory; + this.iso8601DateConverter = iso8601DateConverter; + } + + public InputStream getTableNameEntry(String table) { + String entity = String.format("" + + " " + " " + "<updated>%s</updated>" + "<author>" + + " <name/> " + "</author> " + " <id/> " + " <content type=\"application/xml\">" + + " <m:properties>" + " <d:TableName>%s</d:TableName>" + " </m:properties>" + + " </content> " + "</entry>", iso8601DateConverter.format(dateFactory.getDate()), table); + + try { + return new ByteArrayInputStream(entity.getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public List<TableEntry> parseTableEntries(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed"); + + List<TableEntry> result = new ArrayList<TableEntry>(); + while (!isEndElement(xmlr, "feed")) { + // Process "entry" elements only + if (isStartElement(xmlr, "entry")) { + result.add(parseTableEntry(xmlr)); + } + else { + nextSignificant(xmlr); + } + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "feed"); + expect(xmlr, XMLStreamConstants.END_DOCUMENT); + + return result; + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + public TableEntry parseTableEntry(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + TableEntry result = parseTableEntry(xmlr); + expect(xmlr, XMLStreamConstants.END_DOCUMENT); + + return result; + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + private TableEntry parseTableEntry(XMLStreamReader xmlr) throws XMLStreamException { + TableEntry result = new TableEntry(); + + expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); + + while (!isEndElement(xmlr, "entry")) { + + if (isStartElement(xmlr, "TableName")) { + xmlr.next(); + result.setName(xmlr.getText()); + + nextSignificant(xmlr); + expect(xmlr, XMLStreamConstants.END_ELEMENT, "TableName"); + } + else { + nextSignificant(xmlr); + } + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry"); + + return result; + } + + private void nextSignificant(XMLStreamReader xmlr) throws XMLStreamException { + if (!xmlr.hasNext()) + return; + xmlr.next(); + + while (xmlr.isCharacters()) { + if (!xmlr.hasNext()) + return; + xmlr.next(); + } + } + + private boolean isStartElement(XMLStreamReader xmlr, String localName) { + return xmlr.isStartElement() && localName.equals(xmlr.getLocalName()); + } + + private boolean isEndElement(XMLStreamReader xmlr, String localName) { + return xmlr.isEndElement() && localName.equals(xmlr.getLocalName()); + } + + private void expect(XMLStreamReader xmlr, int eventType) throws XMLStreamException { + expect(xmlr, eventType, null); + } + + private void expect(XMLStreamReader xmlr, int eventType, String localName) throws XMLStreamException { + xmlr.require(eventType, null, localName); + nextSignificant(xmlr); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java new file mode 100644 index 0000000000000..9c44869f23f91 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java @@ -0,0 +1,40 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.InputStream; +import java.io.OutputStream; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +public class DefaultXMLStreamFactory implements XMLStreamFactory { + private final XMLOutputFactory xmlOutputFactory; + private final XMLInputFactory xmlInputFactory; + + public DefaultXMLStreamFactory() { + this.xmlOutputFactory = XMLOutputFactory.newInstance(); + this.xmlInputFactory = XMLInputFactory.newInstance(); + } + + @Override + public XMLStreamWriter getWriter(OutputStream stream) { + try { + return xmlOutputFactory.createXMLStreamWriter(stream); + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + @Override + public XMLStreamReader getReader(InputStream stream) { + try { + return xmlInputFactory.createXMLStreamReader(stream); + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index a771f2e57c70c..48e2f4e675da5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -24,6 +24,8 @@ import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -51,7 +53,7 @@ public TableContract withFilter(ServiceFilter filter) { private ServiceException processCatch(ServiceException e) { log.warn(e.getMessage(), e.getCause()); - return ServiceExceptionFactory.process("blob", e); + return ServiceExceptionFactory.process("table", e); } @Override @@ -107,6 +109,84 @@ public void setServiceProperties(ServiceProperties serviceProperties, TableServi } } + @Override + public void createTable(String table) throws ServiceException { + try { + service.createTable(table); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void createTable(String table, TableServiceOptions options) throws ServiceException { + try { + service.createTable(table, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public GetTableResult getTable(String table) throws ServiceException { + try { + return service.getTable(table); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException { + try { + return service.getTable(table, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void deleteTable(String table) throws ServiceException { + try { + service.deleteTable(table); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void deleteTable(String table, TableServiceOptions options) throws ServiceException { + try { + service.deleteTable(table, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + @Override public QueryTablesResult queryTables() throws ServiceException { try { @@ -132,4 +212,30 @@ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceE throw processCatch(new ServiceException(e)); } } + + @Override + public QueryTablesResult listTables() throws ServiceException { + try { + return service.listTables(); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { + try { + return service.listTables(options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 52b3eb6bbdac5..17326178a9527 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -14,24 +14,37 @@ */ package com.microsoft.windowsazure.services.table.implementation; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import javax.inject.Inject; import javax.inject.Named; +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.core.ServiceFilter; +import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder; +import com.microsoft.windowsazure.services.core.utils.DateFactory; import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter; import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient; import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; import com.microsoft.windowsazure.services.table.TableConfiguration; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.BinaryFilterExpression; +import com.microsoft.windowsazure.services.table.models.ConstantFilterExpression; +import com.microsoft.windowsazure.services.table.models.FilterExpression; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.LitteralFilterExpression; +import com.microsoft.windowsazure.services.table.models.QueryBuilder; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; +import com.microsoft.windowsazure.services.table.models.UnaryFilterExpression; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; @@ -39,55 +52,155 @@ public class TableRestProxy implements TableContract { private static final String API_VERSION = "2011-08-18"; private final HttpURLConnectionClient channel; - private final String accountName; private final String url; private final RFC1123DateConverter dateMapper; + private final ISO8601DateConverter iso8601DateConverter; + private final DateFactory dateFactory; private final ServiceFilter[] filters; private final SharedKeyFilter filter; + private final AtomReaderWriter atomReaderWriter; @Inject - public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.ACCOUNT_NAME) String accountName, - @Named(TableConfiguration.URI) String url, SharedKeyFilter filter) { + public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.URI) String url, + SharedKeyFilter filter, DateFactory dateFactory, ISO8601DateConverter iso8601DateConverter, + AtomReaderWriter atomReaderWriter) { this.channel = channel; - this.accountName = accountName; this.url = url; this.filter = filter; this.dateMapper = new RFC1123DateConverter(); + this.iso8601DateConverter = iso8601DateConverter; this.filters = new ServiceFilter[0]; + this.dateFactory = dateFactory; + this.atomReaderWriter = atomReaderWriter; channel.addFilter(filter); } - public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String accountName, String url, - SharedKeyFilter filter, RFC1123DateConverter dateMapper) { + public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String url, SharedKeyFilter filter, + DateFactory dateFactory, AtomReaderWriter atomReaderWriter, RFC1123DateConverter dateMapper, + ISO8601DateConverter iso8601DateConverter) { this.channel = channel; this.filters = filters; - this.accountName = accountName; this.url = url; this.filter = filter; + this.dateFactory = dateFactory; + this.atomReaderWriter = atomReaderWriter; this.dateMapper = dateMapper; + this.iso8601DateConverter = iso8601DateConverter; } @Override public TableContract withFilter(ServiceFilter filter) { ServiceFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1); newFilters[filters.length] = filter; - return new TableRestProxy(this.channel, newFilters, this.accountName, this.url, this.filter, this.dateMapper); + return new TableRestProxy(this.channel, newFilters, this.url, this.filter, this.dateFactory, + this.atomReaderWriter, this.dateMapper, this.iso8601DateConverter); } private void ThrowIfError(ClientResponse r) { PipelineHelpers.ThrowIfError(r); } + private String encodeODataURIValue(String value) { + //TODO: Unclear if OData value in URI's need to be encoded or not + return value; + } + + private List<String> encodeODataURIValues(List<String> values) { + List<String> list = new ArrayList<String>(); + for (String value : values) { + list.add(encodeODataURIValue(value)); + } + return list; + } + private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } + private WebResource addOptionalQuery(WebResource webResource, QueryBuilder query) { + if (query == null) + return webResource; + + if (query.getFields() != null && query.getFields().size() > 0) { + webResource = addOptionalQueryParam(webResource, "$select", + CommaStringBuilder.join(encodeODataURIValues(query.getFields()))); + } + + if (query.getTop() != null) { + webResource = addOptionalQueryParam(webResource, "$top", encodeODataURIValue(query.getTop().toString())); + } + + if (query.getFilter() != null) { + webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(query.getFilter())); + } + + if (query.getOrderBy() != null) { + webResource = addOptionalQueryParam(webResource, "$orderby", + CommaStringBuilder.join(encodeODataURIValues(query.getOrderBy()))); + } + + if (query.getNextPartitionKey() != null) { + webResource = addOptionalQueryParam(webResource, "NextPartitionKey", + encodeODataURIValue(query.getNextPartitionKey())); + } + + if (query.getNextRowKey() != null) { + webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(query.getNextRowKey())); + } + + return webResource; + } + + private String buildFilterExpression(FilterExpression filter) { + StringBuilder sb = new StringBuilder(); + buildFilterExpression(filter, sb); + return sb.toString(); + } + + private void buildFilterExpression(FilterExpression filter, StringBuilder sb) { + if (filter == null) + return; + + if (filter instanceof LitteralFilterExpression) { + sb.append(((LitteralFilterExpression) filter).getLitteral()); + } + else if (filter instanceof ConstantFilterExpression) { + sb.append("'"); + sb.append(((ConstantFilterExpression) filter).getValue()); + sb.append("'"); + } + else if (filter instanceof UnaryFilterExpression) { + sb.append(((UnaryFilterExpression) filter).getOperator()); + sb.append("("); + buildFilterExpression(((UnaryFilterExpression) filter).getOperand(), sb); + sb.append(")"); + } + else if (filter instanceof BinaryFilterExpression) { + sb.append("("); + buildFilterExpression(((BinaryFilterExpression) filter).getLeft(), sb); + sb.append(" "); + sb.append(((BinaryFilterExpression) filter).getOperator()); + sb.append(" "); + buildFilterExpression(((BinaryFilterExpression) filter).getRight(), sb); + sb.append(")"); + } + } + private Builder addOptionalHeader(Builder builder, String name, Object value) { return PipelineHelpers.addOptionalHeader(builder, name, value); } + private WebResource.Builder addTableRequestHeaders(WebResource.Builder builder) { + builder = addOptionalHeader(builder, "x-ms-version", API_VERSION); + builder = addOptionalHeader(builder, "DataServiceVersion", "1.0;NetFx"); + builder = addOptionalHeader(builder, "MaxDataServiceVersion", "2.0;NetFx"); + builder = addOptionalHeader(builder, "Accept", "application/atom+xml,application/xml"); + builder = addOptionalHeader(builder, "Accept-Charset", "UTF-8"); + return builder; + } + private WebResource getResource(TableServiceOptions options) { WebResource webResource = channel.resource(url).path("/"); webResource = addOptionalQueryParam(webResource, "timeout", options.getTimeout()); @@ -131,6 +244,46 @@ public void setServiceProperties(ServiceProperties serviceProperties, TableServi builder.put(serviceProperties); } + @Override + public GetTableResult getTable(String table) throws ServiceException { + return getTable(table, new TableServiceOptions()); + } + + @Override + public GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("Tables" + "('" + table + "')"); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + ClientResponse response = builder.get(ClientResponse.class); + ThrowIfError(response); + + GetTableResult result = new GetTableResult(); + result.setTable(atomReaderWriter.parseTableEntry(response.getEntityInputStream())); + return result; + } + + @Override + public QueryTablesResult listTables() throws ServiceException { + return listTables(new ListTablesOptions()); + } + + @Override + public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { + // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> uppperBound is prefix + '{' + FilterExpression filter = FilterExpression.and( + FilterExpression.ge(FilterExpression.litteral("TableName"), + FilterExpression.constant(options.getPrefix())), + FilterExpression.le(FilterExpression.litteral("TableName"), + FilterExpression.constant(options.getPrefix() + "{"))); + + QueryTablesOptions queryTableOptions = new QueryTablesOptions(); + queryTableOptions.setTimeout(options.getTimeout()); + queryTableOptions.setQuery(new QueryBuilder().setFilter(filter)); + return queryTables(queryTableOptions); + } + @Override public QueryTablesResult queryTables() throws ServiceException { return queryTables(new QueryTablesOptions()); @@ -138,23 +291,55 @@ public QueryTablesResult queryTables() throws ServiceException { @Override public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { - WebResource webResource = getResource(options).path("Tables" + getTableQuery(options.getQuery())); + WebResource webResource = getResource(options).path("Tables"); + webResource = addOptionalQuery(webResource, options.getQuery()); - WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); ClientResponse response = builder.get(ClientResponse.class); ThrowIfError(response); QueryTablesResult result = new QueryTablesResult(); result.setContinuationToken(response.getHeaders().getFirst("x-ms-continuation-NextTableName")); + result.setTables(atomReaderWriter.parseTableEntries(response.getEntityInputStream())); return result; } - private String getTableQuery(String query) { - if (query == null || query.length() == 0) - return ""; + @Override + public void createTable(String table) throws ServiceException { + createTable(table, new TableServiceOptions()); + + } + + @Override + public void createTable(String table, TableServiceOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("Tables"); - return "(" + query + ")"; + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + builder.entity(atomReaderWriter.getTableNameEntry(table), "application/atom+xml"); + + ClientResponse response = builder.post(ClientResponse.class); + ThrowIfError(response); + } + + @Override + public void deleteTable(String table) throws ServiceException { + deleteTable(table, new TableServiceOptions()); + } + + @Override + public void deleteTable(String table, TableServiceOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("Tables" + "('" + table + "')"); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + builder = addOptionalHeader(builder, "Content-Type", "application/atom+xml"); + + ClientResponse response = builder.delete(ClientResponse.class); + ThrowIfError(response); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java new file mode 100644 index 0000000000000..0e97c0193ab5b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.InputStream; +import java.io.OutputStream; + +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +public interface XMLStreamFactory { + XMLStreamWriter getWriter(OutputStream stream); + + XMLStreamReader getReader(InputStream stream); +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java new file mode 100644 index 0000000000000..25547090206ad --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java @@ -0,0 +1,34 @@ +package com.microsoft.windowsazure.services.table.models; + +public class BinaryFilterExpression extends FilterExpression { + private String operator; + private FilterExpression left; + private FilterExpression right; + + public String getOperator() { + return operator; + } + + public BinaryFilterExpression setOperator(String operator) { + this.operator = operator; + return this; + } + + public FilterExpression getLeft() { + return left; + } + + public BinaryFilterExpression setLeft(FilterExpression left) { + this.left = left; + return this; + } + + public FilterExpression getRight() { + return right; + } + + public BinaryFilterExpression setRight(FilterExpression right) { + this.right = right; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java new file mode 100644 index 0000000000000..4c38168ca2955 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class ConstantFilterExpression extends FilterExpression { + private Object value; + + public Object getValue() { + return value; + } + + public ConstantFilterExpression setValue(Object value) { + this.value = value; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java new file mode 100644 index 0000000000000..655a53838e755 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java @@ -0,0 +1,47 @@ +package com.microsoft.windowsazure.services.table.models; + +public class FilterExpression { + public static UnaryFilterExpression not(FilterExpression operand) { + return new UnaryFilterExpression().setOperator("not").setOperand(operand); + } + + public static BinaryFilterExpression and(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("and").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression or(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("or").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression eq(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("eq").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression ne(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("ne").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression ge(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("ge").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression gt(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("gt").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression lt(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("lt").setLeft(left).setRight(right); + } + + public static BinaryFilterExpression le(FilterExpression left, FilterExpression right) { + return new BinaryFilterExpression().setOperator("le").setLeft(left).setRight(right); + } + + public static ConstantFilterExpression constant(Object value) { + return new ConstantFilterExpression().setValue(value); + } + + public static LitteralFilterExpression litteral(String value) { + return new LitteralFilterExpression().setLitteral(value); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java new file mode 100644 index 0000000000000..2978c465cebd2 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.models; + +public class GetTableResult { + private TableEntry table; + + public TableEntry getTable() { + return table; + } + + public void setTable(TableEntry table) { + this.table = table; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java new file mode 100644 index 0000000000000..25f042b564f0f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java @@ -0,0 +1,20 @@ +package com.microsoft.windowsazure.services.table.models; + +public class ListTablesOptions extends TableServiceOptions { + private String prefix; + + @Override + public ListTablesOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public String getPrefix() { + return prefix; + } + + public ListTablesOptions setPrefix(String prefix) { + this.prefix = prefix; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java new file mode 100644 index 0000000000000..8c4d857624e3b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class LitteralFilterExpression extends FilterExpression { + private String litteral; + + public String getLitteral() { + return litteral; + } + + public LitteralFilterExpression setLitteral(String litteral) { + this.litteral = litteral; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java new file mode 100644 index 0000000000000..7c915cf53cb49 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java @@ -0,0 +1,96 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.List; + +public class QueryBuilder { + private List<String> fields; + private String from; + private FilterExpression filter; + private List<String> orderBy; + private Integer top; + private String partitionKey; + private String nextPartitionKey; + private String rowKey; + private String nextRowKey; + + public List<String> getFields() { + return fields; + } + + public QueryBuilder setFields(List<String> fields) { + this.fields = fields; + return this; + } + + public String getFrom() { + return from; + } + + public QueryBuilder setFrom(String from) { + this.from = from; + return this; + } + + public FilterExpression getFilter() { + return filter; + } + + public QueryBuilder setFilter(FilterExpression where) { + this.filter = where; + return this; + } + + public List<String> getOrderBy() { + return orderBy; + } + + public QueryBuilder setOrderBy(List<String> orderBy) { + this.orderBy = orderBy; + return this; + } + + public Integer getTop() { + return top; + } + + public QueryBuilder setTop(Integer top) { + this.top = top; + return this; + } + + public String getPartitionKey() { + return partitionKey; + } + + public QueryBuilder setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + return this; + } + + public String getNextPartitionKey() { + return nextPartitionKey; + } + + public QueryBuilder setNextPartitionKey(String nextPartitionKey) { + this.nextPartitionKey = nextPartitionKey; + return this; + } + + public String getRowKey() { + return rowKey; + } + + public QueryBuilder setRowKey(String rowKey) { + this.rowKey = rowKey; + return this; + } + + public String getNextRowKey() { + return nextRowKey; + } + + public QueryBuilder setNextRowKey(String nextRowKey) { + this.nextRowKey = nextRowKey; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index e60e10bfe4ef5..d4896fa674988 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -1,7 +1,7 @@ package com.microsoft.windowsazure.services.table.models; public class QueryTablesOptions extends TableServiceOptions { - private String query; + private QueryBuilder query; @Override public QueryTablesOptions setTimeout(Integer timeout) { @@ -9,11 +9,11 @@ public QueryTablesOptions setTimeout(Integer timeout) { return this; } - public String getQuery() { + public QueryBuilder getQuery() { return query; } - public QueryTablesOptions setQuery(String query) { + public QueryTablesOptions setQuery(QueryBuilder query) { this.query = query; return this; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java index 0215444e944bb..3816ffe5899af 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -1,7 +1,10 @@ package com.microsoft.windowsazure.services.table.models; +import java.util.List; + public class QueryTablesResult { private String continuationToken; + private List<TableEntry> tables; public String getContinuationToken() { return continuationToken; @@ -11,4 +14,11 @@ public void setContinuationToken(String continuationToken) { this.continuationToken = continuationToken; } + public List<TableEntry> getTables() { + return tables; + } + + public void setTables(List<TableEntry> tables) { + this.tables = tables; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java new file mode 100644 index 0000000000000..917cecc3409c5 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.models; + +public class TableEntry { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java new file mode 100644 index 0000000000000..3e0efd984bffa --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java @@ -0,0 +1,24 @@ +package com.microsoft.windowsazure.services.table.models; + +public class UnaryFilterExpression extends FilterExpression { + private String operator; + private FilterExpression operand; + + public String getOperator() { + return operator; + } + + public UnaryFilterExpression setOperator(String operator) { + this.operator = operator; + return this; + } + + public FilterExpression getOperand() { + return operand; + } + + public UnaryFilterExpression setOperand(FilterExpression operand) { + this.operand = operand; + return this; + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java new file mode 100644 index 0000000000000..3cf03c788639f --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java @@ -0,0 +1,81 @@ +/** + * Copyright 2011 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; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.List; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; +import com.microsoft.windowsazure.services.core.utils.DefaultDateFactory; +import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; +import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; +import com.microsoft.windowsazure.services.table.models.TableEntry; + +public class AtomReaderWriterTests extends IntegrationTestBase { + @Test + public void parseTableEntriesWorks() throws Exception { + // Arrange + AtomReaderWriter atom = new AtomReaderWriter(new DefaultXMLStreamFactory(), new DefaultDateFactory(), + new ISO8601DateConverter()); + String feed = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n" + + "<feed xml:base=\"http://rpaquaytest.table.core.windows.net/\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\r\n" + + " <title type=\"text\">Tables\r\n" + + " http://rpaquaytest.table.core.windows.net/Tables\r\n" + + " 2012-01-10T21:23:30Z\r\n" + + " \r\n" + + " \r\n" + + " http://rpaquaytest.table.core.windows.net/Tables('sdktest1')\r\n" + + " \r\n" + + " 2012-01-10T21:23:30Z\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " sdktest1\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " http://rpaquaytest.table.core.windows.net/Tables('sdktest10')\r\n" + + " \r\n" + + " 2012-01-10T21:23:30Z\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + " \r\n" + + " sdktest10\r\n" + " \r\n" + + " \r\n" + " \r\n" + "\r\n"; + InputStream stream = new ByteArrayInputStream(feed.getBytes("UTF-8")); + + // Act + List entries = atom.parseTableEntries(stream); + + // Assert + assertNotNull(entries); + assertEquals(2, entries.size()); + assertEquals("sdktest1", entries.get(0).getName()); + assertEquals("sdktest10", entries.get(1).getName()); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 2c67bbce8c0ea..434b10be1be36 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -16,13 +16,98 @@ import static org.junit.Assert.*; +import java.util.HashSet; +import java.util.Set; + +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; +import com.microsoft.windowsazure.services.table.models.TableEntry; public class TableServiceIntegrationTest extends IntegrationTestBase { + private static final String testTablesPrefix = "sdktest"; + private static final String createableTablesPrefix = "csdktest"; + private static String TEST_TABLE_1; + private static String TEST_TABLE_2; + private static String TEST_TABLE_3; + private static String TEST_TABLE_4; + private static String CREATABLE_TABLE_1; + private static String CREATABLE_TABLE_2; + private static String CREATABLE_TABLE_3; + private static String[] creatableTables; + private static String[] testTables; + + @BeforeClass + public static void setup() throws Exception { + // Setup container names array (list of container names used by + // integration tests) + testTables = new String[10]; + for (int i = 0; i < testTables.length; i++) { + testTables[i] = String.format("%s%d", testTablesPrefix, i + 1); + } + + creatableTables = new String[10]; + for (int i = 0; i < creatableTables.length; i++) { + creatableTables[i] = String.format("%s%d", createableTablesPrefix, i + 1); + } + + TEST_TABLE_1 = testTables[0]; + TEST_TABLE_2 = testTables[1]; + TEST_TABLE_3 = testTables[2]; + TEST_TABLE_4 = testTables[3]; + + CREATABLE_TABLE_1 = creatableTables[0]; + CREATABLE_TABLE_2 = creatableTables[1]; + CREATABLE_TABLE_3 = creatableTables[2]; + + // Create all test containers and their content + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + createTables(service, testTablesPrefix, testTables); + } + + @AfterClass + public static void cleanup() throws Exception { + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + deleteTables(service, testTablesPrefix, testTables); + deleteTables(service, createableTablesPrefix, creatableTables); + } + + private static void createTables(TableContract service, String prefix, String[] list) throws Exception { + Set containers = listTables(service, prefix); + for (String item : list) { + if (!containers.contains(item)) { + service.createTable(item); + } + } + } + + private static void deleteTables(TableContract service, String prefix, String[] list) throws Exception { + Set containers = listTables(service, prefix); + for (String item : list) { + if (containers.contains(item)) { + service.deleteTable(item); + } + } + } + + private static Set listTables(TableContract service, String prefix) throws Exception { + HashSet result = new HashSet(); + QueryTablesResult list = service.listTables(new ListTablesOptions().setPrefix(prefix)); + for (TableEntry item : list.getTables()) { + result.add(item.getName()); + } + return result; + } @Test public void getServicePropertiesWorks() throws Exception { @@ -76,6 +161,54 @@ public void setServicePropertiesWorks() throws Exception { assertNotNull(props.getMetrics().getVersion()); } + @Test + public void createTablesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + Exception error; + try { + service.getTable(CREATABLE_TABLE_1); + error = null; + } + catch (Exception e) { + error = e; + } + service.createTable(CREATABLE_TABLE_1); + GetTableResult result = service.getTable(CREATABLE_TABLE_1); + + // Assert + assertNotNull(error); + assertNotNull(result); + } + + @Test + public void deleteTablesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + service.createTable(CREATABLE_TABLE_2); + GetTableResult result = service.getTable(CREATABLE_TABLE_2); + + service.deleteTable(CREATABLE_TABLE_2); + Exception error; + try { + service.getTable(CREATABLE_TABLE_2); + error = null; + } + catch (Exception e) { + error = e; + } + + // Assert + assertNotNull(error); + assertNotNull(result); + } + @Test public void queryTablesWorks() throws Exception { // Arrange @@ -88,4 +221,44 @@ public void queryTablesWorks() throws Exception { // Assert assertNotNull(result); } + + @Test + public void listTablesWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + QueryTablesResult result = service.listTables(); + + // Assert + assertNotNull(result); + } + + @Test + public void queryTablesWithPrefixWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + QueryTablesResult result = service.listTables(new ListTablesOptions().setPrefix(testTablesPrefix)); + + // Assert + assertNotNull(result); + } + + @Test + public void getTableWorks() throws Exception { + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + System.out.println("getTable() test"); + GetTableResult result = service.getTable(TEST_TABLE_1); + + // Assert + assertNotNull(result); + } } From f481f385a4b65bafede2accfa246283fc037ca07 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 12:56:16 -0800 Subject: [PATCH 05/76] Add support for "insertEntity" --- .../implementation/ISO8601DateConverter.java | 25 +-- .../services/table/EdmValueConverter.java | 7 + .../windowsazure/services/table/Exports.java | 2 + .../services/table/TableContract.java | 6 + .../implementation/AtomReaderWriter.java | 157 +++++++++++++++--- .../DefaultEdmValueConterter.java | 65 ++++++++ .../DefaultXMLStreamFactory.java | 2 +- .../table/implementation/SharedKeyFilter.java | 2 +- .../TableExceptionProcessor.java | 29 ++++ .../table/implementation/TableRestProxy.java | 30 +++- .../services/table/models/EdmType.java | 12 ++ .../services/table/models/Entity.java | 67 ++++++++ .../services/table/models/GetTableResult.java | 10 +- .../table/models/InsertEntityResult.java | 13 ++ .../services/table/models/Property.java | 24 +++ .../services/table/AtomReaderWriterTests.java | 3 +- .../table/TableServiceIntegrationTest.java | 51 +++++- 17 files changed, 450 insertions(+), 55 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index a5f02e0256372..08d7d6ea9ce8a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.implementation; @@ -36,15 +36,6 @@ public Date parse(String date) throws ParseException { return getFormat().parse(date); } - public Date parseNoThrow(String date) { - try { - return parse(date); - } - catch (ParseException e) { - return null; - } - } - private DateFormat getFormat() { DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java new file mode 100644 index 0000000000000..e7582255ef590 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java @@ -0,0 +1,7 @@ +package com.microsoft.windowsazure.services.table; + +public interface EdmValueConverter { + String serialize(String edmType, Object value); + + Object deserialize(String edmType, String value); +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java index c36841928b064..21ad0784df4b4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -16,6 +16,7 @@ import com.microsoft.windowsazure.services.core.Builder; import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; +import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter; import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter; @@ -33,5 +34,6 @@ public void register(Builder.Registry registry) { registry.add(SharedKeyFilter.class); registry.add(XMLStreamFactory.class, DefaultXMLStreamFactory.class); registry.add(AtomReaderWriter.class); + registry.add(EdmValueConverter.class, DefaultEdmValueConterter.class); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 4d9776bde42cb..8fc4caefeadc2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -16,8 +16,10 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; @@ -52,4 +54,8 @@ public interface TableContract extends FilterableService { QueryTablesResult queryTables() throws ServiceException; QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException; + + InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException; + + InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index bd8439e5ee430..c1943ebdcd1eb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -1,48 +1,77 @@ package com.microsoft.windowsazure.services.table.implementation; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import javax.inject.Inject; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.core.utils.DateFactory; +import com.microsoft.windowsazure.services.table.EdmValueConverter; +import com.microsoft.windowsazure.services.table.models.Entity; +import com.microsoft.windowsazure.services.table.models.Property; import com.microsoft.windowsazure.services.table.models.TableEntry; public class AtomReaderWriter { private final XMLStreamFactory xmlStreamFactory; private final DateFactory dateFactory; private final ISO8601DateConverter iso8601DateConverter; + private final EdmValueConverter edmValueConverter; @Inject public AtomReaderWriter(XMLStreamFactory xmlStreamFactory, DateFactory dateFactory, - ISO8601DateConverter iso8601DateConverter) { + ISO8601DateConverter iso8601DateConverter, EdmValueConverter edmValueConverter) { this.xmlStreamFactory = xmlStreamFactory; this.dateFactory = dateFactory; this.iso8601DateConverter = iso8601DateConverter; + this.edmValueConverter = edmValueConverter; } - public InputStream getTableNameEntry(String table) { - String entity = String.format("" - + " " + " " + "<updated>%s</updated>" + "<author>" - + " <name/> " + "</author> " + " <id/> " + " <content type=\"application/xml\">" - + " <m:properties>" + " <d:TableName>%s</d:TableName>" + " </m:properties>" - + " </content> " + "</entry>", iso8601DateConverter.format(dateFactory.getDate()), table); + public InputStream generateTableEntry(String table) { + final String tableTemp = table; + return generateEntry(new PropertiesWriter() { + @Override + public void write(XMLStreamWriter writer) throws XMLStreamException { + writer.writeStartElement("d:TableName"); + writer.writeCharacters(tableTemp); + writer.writeEndElement(); // d:TableName + } + }); + } - try { - return new ByteArrayInputStream(entity.getBytes("UTF-8")); - } - catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + public InputStream generateEntityEntry(Entity entity) { + final Entity entityTemp = entity; + return generateEntry(new PropertiesWriter() { + @Override + public void write(XMLStreamWriter writer) throws XMLStreamException { + for (Entry<String, Property> entry : entityTemp.getProperties().entrySet()) { + writer.writeStartElement("d:" + entry.getKey()); + + String edmType = entry.getValue().getEdmType(); + if (edmType != null) { + writer.writeAttribute("m:type", edmType); + } + + String value = edmValueConverter.serialize(edmType, entry.getValue().getValue()); + if (value != null) { + writer.writeCharacters(value); + } + + writer.writeEndElement(); // property name + + } + } + }); } public List<TableEntry> parseTableEntries(InputStream stream) { @@ -88,19 +117,105 @@ public TableEntry parseTableEntry(InputStream stream) { } } + public Entity parseEntityEntry(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + Map<String, Property> properties = parseEntryProperties(xmlr); + expect(xmlr, XMLStreamConstants.END_DOCUMENT); + + return new Entity().setProperties(properties); + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + private interface PropertiesWriter { + void write(XMLStreamWriter writer) throws XMLStreamException; + } + + private InputStream generateEntry(PropertiesWriter propertiesWriter) { + try { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + XMLStreamWriter writer = xmlStreamFactory.getWriter(stream); + writer.writeStartDocument("utf-8", "1.0"); + + writer.writeStartElement("entry"); + writer.writeAttribute("xmlns:d", "http://schemas.microsoft.com/ado/2007/08/dataservices"); + writer.writeAttribute("xmlns:m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"); + writer.writeAttribute("xmlns", "http://www.w3.org/2005/Atom"); + + writer.writeStartElement("title"); + writer.writeEndElement(); // title + + writer.writeStartElement("updated"); + writer.writeCharacters(iso8601DateConverter.format(dateFactory.getDate())); + writer.writeEndElement(); // updated + + writer.writeStartElement("author"); + writer.writeStartElement("name"); + writer.writeEndElement(); // name + writer.writeEndElement(); // author + + writer.writeStartElement("id"); + writer.writeEndElement(); // id + + writer.writeStartElement("content"); + writer.writeAttribute("type", "application/xml"); + + writer.writeStartElement("m:properties"); + propertiesWriter.write(writer); + writer.writeEndElement(); // m:properties + + writer.writeEndElement(); // content + + writer.writeEndElement(); // entry + + writer.writeEndDocument(); + writer.close(); + + return new ByteArrayInputStream(stream.toByteArray()); + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + private TableEntry parseTableEntry(XMLStreamReader xmlr) throws XMLStreamException { + Map<String, Property> properties = parseEntryProperties(xmlr); + TableEntry result = new TableEntry(); + result.setName((String) properties.get("TableName").getValue()); + return result; + } + + private Map<String, Property> parseEntryProperties(XMLStreamReader xmlr) throws XMLStreamException { + Map<String, Property> result = new HashMap<String, Property>(); expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); while (!isEndElement(xmlr, "entry")) { - if (isStartElement(xmlr, "TableName")) { - xmlr.next(); - result.setName(xmlr.getText()); - + if (isStartElement(xmlr, "properties")) { nextSignificant(xmlr); - expect(xmlr, XMLStreamConstants.END_ELEMENT, "TableName"); + + while (!isEndElement(xmlr, "properties")) { + String name = xmlr.getLocalName(); + String edmType = xmlr.getAttributeValue(null, "type"); + + xmlr.next(); + String serializedValue = xmlr.getText(); + Object value = edmValueConverter.deserialize(edmType, serializedValue); + + result.put(name, new Property().setEdmType(edmType).setValue(value)); + + nextSignificant(xmlr); + expect(xmlr, XMLStreamConstants.END_ELEMENT, name); + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "properties"); } else { nextSignificant(xmlr); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java new file mode 100644 index 0000000000000..b062b3394ab9d --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -0,0 +1,65 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.text.ParseException; +import java.util.Date; + +import javax.inject.Inject; + +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; +import com.microsoft.windowsazure.services.table.EdmValueConverter; +import com.microsoft.windowsazure.services.table.models.EdmType; + +public class DefaultEdmValueConterter implements EdmValueConverter { + + private final ISO8601DateConverter iso8601DateConverter; + + @Inject + public DefaultEdmValueConterter(ISO8601DateConverter iso8601DateConverter) { + this.iso8601DateConverter = iso8601DateConverter; + } + + @Override + public String serialize(String edmType, Object value) { + if (value == null) + return null; + + String serializedValue; + if (value instanceof Date) { + serializedValue = iso8601DateConverter.format((Date) value); + } + else { + serializedValue = value.toString(); + } + + return serializedValue; + } + + @Override + public Object deserialize(String edmType, String value) { + if (edmType == null) + return value; + + if (EdmType.DATETIME.equals(edmType)) { + try { + return iso8601DateConverter.parse(value); + } + catch (ParseException e) { + throw new RuntimeException(e); + } + } + else if (EdmType.BOOLEAN.equals(edmType)) { + return Boolean.parseBoolean(value); + } + else if (EdmType.DOUBLE.equals(edmType)) { + return Double.parseDouble(value); + } + else if (EdmType.INT32.equals(edmType)) { + return Integer.parseInt(value); + } + else if (EdmType.INT64.equals(edmType)) { + return Long.parseLong(value); + } + + return value; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java index 9c44869f23f91..8504c88fa349f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java @@ -21,7 +21,7 @@ public DefaultXMLStreamFactory() { @Override public XMLStreamWriter getWriter(OutputStream stream) { try { - return xmlOutputFactory.createXMLStreamWriter(stream); + return xmlOutputFactory.createXMLStreamWriter(stream, "UTF-8"); } catch (XMLStreamException e) { throw new RuntimeException(e); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java index 19a5cc737bc54..79305ce758c84 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -56,7 +56,7 @@ public void sign(ClientRequest cr) { log.debug(String.format("String to sign: \"%s\"", stringToSign)); } //TODO: Remove or comment the following line - System.out.println(String.format("String to sign: \"%s\"", stringToSign)); + //System.out.println(String.format("String to sign: \"%s\"", stringToSign)); String signature = this.getSigner().sign(stringToSign); cr.getHeaders().putSingle("Authorization", "SharedKey " + this.getAccountName() + ":" + signature); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 48e2f4e675da5..64215de5c6042 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -23,8 +23,10 @@ import com.microsoft.windowsazure.services.core.ServiceFilter; import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; @@ -238,4 +240,31 @@ public QueryTablesResult listTables(ListTablesOptions options) throws ServiceExc throw processCatch(new ServiceException(e)); } } + + @Override + public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException { + try { + return service.insertEntity(table, entity); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + try { + return service.insertEntity(table, entity, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 17326178a9527..4a2dbc92c29f5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -34,9 +34,11 @@ import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.BinaryFilterExpression; import com.microsoft.windowsazure.services.table.models.ConstantFilterExpression; +import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.FilterExpression; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.LitteralFilterExpression; import com.microsoft.windowsazure.services.table.models.QueryBuilder; @@ -260,7 +262,7 @@ public GetTableResult getTable(String table, TableServiceOptions options) throws ThrowIfError(response); GetTableResult result = new GetTableResult(); - result.setTable(atomReaderWriter.parseTableEntry(response.getEntityInputStream())); + result.setTableEntry(atomReaderWriter.parseTableEntry(response.getEntityInputStream())); return result; } @@ -320,7 +322,7 @@ public void createTable(String table, TableServiceOptions options) throws Servic WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); - builder.entity(atomReaderWriter.getTableNameEntry(table), "application/atom+xml"); + builder.entity(atomReaderWriter.generateTableEntry(table), "application/atom+xml"); ClientResponse response = builder.post(ClientResponse.class); ThrowIfError(response); @@ -342,4 +344,28 @@ public void deleteTable(String table, TableServiceOptions options) throws Servic ClientResponse response = builder.delete(ClientResponse.class); ThrowIfError(response); } + + @Override + public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException { + return insertEntity(table, entity, new TableServiceOptions()); + } + + @Override + public InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + WebResource webResource = getResource(options).path(table); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); + + ClientResponse response = builder.post(ClientResponse.class); + ThrowIfError(response); + + InsertEntityResult result = new InsertEntityResult(); + result.setEntity(atomReaderWriter.parseEntityEntry(response.getEntityInputStream())); + + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java new file mode 100644 index 0000000000000..5a82435b93375 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java @@ -0,0 +1,12 @@ +package com.microsoft.windowsazure.services.table.models; + +public class EdmType { + public static final String DATETIME = "Edm.DateTime"; + public static final String BINARY = "Edm.Binary"; + public static final String BOOLEAN = "Edm.Boolean"; + public static final String DOUBLE = "Edm.Double"; + public static final String GUID = "Edm.Guid"; + public static final String INT32 = "Edm.Int32"; + public static final String INT64 = "Edm.Int64"; + public static final String STRING = "Edm.String"; +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java new file mode 100644 index 0000000000000..de13eee4740fe --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -0,0 +1,67 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class Entity { + private Map<String, Property> properties = new HashMap<String, Property>(); + + public String getPartitionKey() { + Property p = getProperty("PartitionKey"); + return p == null ? null : (String) p.getValue(); + } + + public Entity setPartitionKey(String partitionKey) { + setProperty("PartitionKey", null, partitionKey); + return this; + } + + public String getRowKey() { + Property p = getProperty("RowKey"); + return p == null ? null : (String) p.getValue(); + } + + public Entity setRowKey(String rowKey) { + setProperty("RowKey", null, rowKey); + return this; + } + + public Date getTimestamp() { + Property p = getProperty("Timestamp"); + return p == null ? null : (Date) p.getValue(); + } + + public Entity setTimestamp(Date timestamp) { + setProperty("Timestamp", null, timestamp); + return this; + } + + public Map<String, Property> getProperties() { + return properties; + } + + public Entity setProperties(Map<String, Property> properties) { + this.properties = properties; + return this; + } + + public Property getProperty(String name) { + return properties.get(name); + } + + public Entity setProperty(String name, Property property) { + this.properties.put(name, property); + return this; + } + + public Entity setProperty(String name, String edmType, Object value) { + setProperty(name, new Property().setEdmType(edmType).setValue(value)); + return this; + } + + public Object getPropertyValue(String name) { + Property p = getProperty(name); + return p == null ? null : p.getValue(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java index 2978c465cebd2..de199b24f075f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java @@ -1,13 +1,13 @@ package com.microsoft.windowsazure.services.table.models; public class GetTableResult { - private TableEntry table; + private TableEntry tableEntry; - public TableEntry getTable() { - return table; + public TableEntry getTableEntry() { + return tableEntry; } - public void setTable(TableEntry table) { - this.table = table; + public void setTableEntry(TableEntry tableEntry) { + this.tableEntry = tableEntry; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java new file mode 100644 index 0000000000000..666fc9634da3e --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.models; + +public class InsertEntityResult { + private Entity entity; + + public Entity getEntity() { + return entity; + } + + public void setEntity(Entity entity) { + this.entity = entity; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java new file mode 100644 index 0000000000000..1e90530e04f06 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java @@ -0,0 +1,24 @@ +package com.microsoft.windowsazure.services.table.models; + +public class Property { + private String edmType; + private Object value; + + public String getEdmType() { + return edmType; + } + + public Property setEdmType(String edmType) { + this.edmType = edmType; + return this; + } + + public Object getValue() { + return value; + } + + public Property setValue(Object value) { + this.value = value; + return this; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java index 3cf03c788639f..9aebd223e75bd 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java @@ -25,6 +25,7 @@ import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.core.utils.DefaultDateFactory; import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; +import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter; import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; import com.microsoft.windowsazure.services.table.models.TableEntry; @@ -33,7 +34,7 @@ public class AtomReaderWriterTests extends IntegrationTestBase { public void parseTableEntriesWorks() throws Exception { // Arrange AtomReaderWriter atom = new AtomReaderWriter(new DefaultXMLStreamFactory(), new DefaultDateFactory(), - new ISO8601DateConverter()); + new ISO8601DateConverter(), new DefaultEdmValueConterter(new ISO8601DateConverter())); String feed = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n" + "<feed xml:base=\"http://rpaquaytest.table.core.windows.net/\" xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\r\n" + " <title type=\"text\">Tables\r\n" diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 434b10be1be36..3ecfde9f2b5a1 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; +import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -24,7 +25,10 @@ import org.junit.Test; import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.table.models.EdmType; +import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -35,11 +39,11 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static final String createableTablesPrefix = "csdktest"; private static String TEST_TABLE_1; private static String TEST_TABLE_2; - private static String TEST_TABLE_3; - private static String TEST_TABLE_4; + //private static String TEST_TABLE_3; + //private static String TEST_TABLE_4; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; - private static String CREATABLE_TABLE_3; + //private static String CREATABLE_TABLE_3; private static String[] creatableTables; private static String[] testTables; @@ -59,12 +63,12 @@ public static void setup() throws Exception { TEST_TABLE_1 = testTables[0]; TEST_TABLE_2 = testTables[1]; - TEST_TABLE_3 = testTables[2]; - TEST_TABLE_4 = testTables[3]; + //TEST_TABLE_3 = testTables[2]; + //TEST_TABLE_4 = testTables[3]; CREATABLE_TABLE_1 = creatableTables[0]; CREATABLE_TABLE_2 = creatableTables[1]; - CREATABLE_TABLE_3 = creatableTables[2]; + //CREATABLE_TABLE_3 = creatableTables[2]; // Create all test containers and their content Configuration config = createConfiguration(); @@ -255,10 +259,43 @@ public void getTableWorks() throws Exception { TableContract service = TableService.create(config); // Act - System.out.println("getTable() test"); GetTableResult result = service.getTable(TEST_TABLE_1); // Assert assertNotNull(result); } + + @Test + public void insertEntityWorks() throws Exception { + System.out.println("insertEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + InsertEntityResult result = service.insertEntity( + TEST_TABLE_2, + new Entity().setPartitionKey("001").setRowKey("002").setProperty("test", EdmType.BOOLEAN, true) + .setProperty("test2", EdmType.STRING, "value").setProperty("test3", EdmType.INT32, 3) + .setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date())); + + // Assert + assertNotNull(result); + assertNotNull(result.getEntity()); + assertEquals("001", result.getEntity().getPartitionKey()); + assertEquals("002", result.getEntity().getRowKey()); + assertNotNull(result.getEntity().getTimestamp()); + assertNotNull(result.getEntity().getProperty("test")); + assertEquals(true, result.getEntity().getProperty("test").getValue()); + assertNotNull(result.getEntity().getProperty("test2")); + assertEquals("value", result.getEntity().getProperty("test2").getValue()); + assertNotNull(result.getEntity().getProperty("test3")); + assertEquals(3, result.getEntity().getProperty("test3").getValue()); + assertNotNull(result.getEntity().getProperty("test4")); + assertEquals(12345678901L, result.getEntity().getProperty("test4").getValue()); + assertNotNull(result.getEntity().getProperty("test5")); + assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); + } } From 28a3cecdd169da24a88f2d4738ed0f38ae86cd75 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 13:41:50 -0800 Subject: [PATCH 06/76] Whitespaces --- .../services/table/TableServiceIntegrationTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 3ecfde9f2b5a1..f07d3a1bb4685 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -284,17 +284,23 @@ public void insertEntityWorks() throws Exception { // Assert assertNotNull(result); assertNotNull(result.getEntity()); + assertEquals("001", result.getEntity().getPartitionKey()); assertEquals("002", result.getEntity().getRowKey()); assertNotNull(result.getEntity().getTimestamp()); + assertNotNull(result.getEntity().getProperty("test")); assertEquals(true, result.getEntity().getProperty("test").getValue()); + assertNotNull(result.getEntity().getProperty("test2")); assertEquals("value", result.getEntity().getProperty("test2").getValue()); + assertNotNull(result.getEntity().getProperty("test3")); assertEquals(3, result.getEntity().getProperty("test3").getValue()); + assertNotNull(result.getEntity().getProperty("test4")); assertEquals(12345678901L, result.getEntity().getProperty("test4").getValue()); + assertNotNull(result.getEntity().getProperty("test5")); assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); } From b3fc5506d0e5d0f9e6bebe1122e7347191dc9a8f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 14:31:19 -0800 Subject: [PATCH 07/76] Add support for "updateEntity" --- .../services/table/TableContract.java | 5 ++ .../implementation/AtomReaderWriter.java | 69 +++++++++++-------- .../TableExceptionProcessor.java | 28 ++++++++ .../table/implementation/TableRestProxy.java | 32 +++++++++ .../services/table/models/Entity.java | 10 +++ .../table/models/UpdateEntityResult.java | 13 ++++ .../table/TableServiceIntegrationTest.java | 23 +++++++ 7 files changed, 153 insertions(+), 27 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 8fc4caefeadc2..4b1611c61e86e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -25,6 +25,7 @@ import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; +import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; public interface TableContract extends FilterableService { GetServicePropertiesResult getServiceProperties() throws ServiceException; @@ -58,4 +59,8 @@ public interface TableContract extends FilterableService { InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException; InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + + UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException; + + UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index c1943ebdcd1eb..0f85fcede8cb1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -120,12 +120,26 @@ public TableEntry parseTableEntry(InputStream stream) { public Entity parseEntityEntry(InputStream stream) { try { XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + Entity result = new Entity(); expect(xmlr, XMLStreamConstants.START_DOCUMENT); - Map properties = parseEntryProperties(xmlr); + + result.setEtag(xmlr.getAttributeValue(null, "etag")); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); + + while (!isEndElement(xmlr, "entry")) { + if (isStartElement(xmlr, "properties")) { + result.setProperties(parseEntryProperties(xmlr)); + } + else { + nextSignificant(xmlr); + } + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry"); expect(xmlr, XMLStreamConstants.END_DOCUMENT); - return new Entity().setProperties(properties); + return result; } catch (XMLStreamException e) { throw new RuntimeException(e); @@ -184,38 +198,15 @@ private InputStream generateEntry(PropertiesWriter propertiesWriter) { } private TableEntry parseTableEntry(XMLStreamReader xmlr) throws XMLStreamException { - Map properties = parseEntryProperties(xmlr); - TableEntry result = new TableEntry(); - result.setName((String) properties.get("TableName").getValue()); - return result; - } - - private Map parseEntryProperties(XMLStreamReader xmlr) throws XMLStreamException { - Map result = new HashMap(); expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); while (!isEndElement(xmlr, "entry")) { - if (isStartElement(xmlr, "properties")) { - nextSignificant(xmlr); - - while (!isEndElement(xmlr, "properties")) { - String name = xmlr.getLocalName(); - String edmType = xmlr.getAttributeValue(null, "type"); - - xmlr.next(); - String serializedValue = xmlr.getText(); - Object value = edmValueConverter.deserialize(edmType, serializedValue); + Map properties = parseEntryProperties(xmlr); - result.put(name, new Property().setEdmType(edmType).setValue(value)); - - nextSignificant(xmlr); - expect(xmlr, XMLStreamConstants.END_ELEMENT, name); - } - - expect(xmlr, XMLStreamConstants.END_ELEMENT, "properties"); + result.setName((String) properties.get("TableName").getValue()); } else { nextSignificant(xmlr); @@ -227,6 +218,30 @@ private Map parseEntryProperties(XMLStreamReader xmlr) throws return result; } + private Map parseEntryProperties(XMLStreamReader xmlr) throws XMLStreamException { + Map result = new HashMap(); + + expect(xmlr, XMLStreamConstants.START_ELEMENT, "properties"); + + while (!isEndElement(xmlr, "properties")) { + String name = xmlr.getLocalName(); + String edmType = xmlr.getAttributeValue(null, "type"); + + xmlr.next(); + String serializedValue = xmlr.getText(); + Object value = edmValueConverter.deserialize(edmType, serializedValue); + + result.put(name, new Property().setEdmType(edmType).setValue(value)); + + nextSignificant(xmlr); + expect(xmlr, XMLStreamConstants.END_ELEMENT, name); + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "properties"); + + return result; + } + private void nextSignificant(XMLStreamReader xmlr) throws XMLStreamException { if (!xmlr.hasNext()) return; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 64215de5c6042..8737038654b6a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -32,6 +32,7 @@ import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; +import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.UniformInterfaceException; @@ -267,4 +268,31 @@ public InsertEntityResult insertEntity(String table, Entity entity, TableService throw processCatch(new ServiceException(e)); } } + + @Override + public UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException { + try { + return service.updateEntity(table, entity); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + try { + return service.updateEntity(table, entity, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 4a2dbc92c29f5..852545c8b7752 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -47,6 +47,7 @@ import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.microsoft.windowsazure.services.table.models.UnaryFilterExpression; +import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; @@ -203,6 +204,11 @@ private WebResource.Builder addTableRequestHeaders(WebResource.Builder builder) return builder; } + private WebResource.Builder addIfMatchHeader(WebResource.Builder builder, String eTag) { + builder = addOptionalHeader(builder, "If-Match", eTag == null ? "*" : eTag); + return builder; + } + private WebResource getResource(TableServiceOptions options) { WebResource webResource = channel.resource(url).path("/"); webResource = addOptionalQueryParam(webResource, "timeout", options.getTimeout()); @@ -368,4 +374,30 @@ public InsertEntityResult insertEntity(String table, Entity entity, TableService return result; } + + @Override + public UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException { + return updateEntity(table, entity, new TableServiceOptions()); + } + + @Override + public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + WebResource webResource = getResource(options).path( + table + "(" + "PartitionKey='" + entity.getPartitionKey() + "',RowKey='" + entity.getRowKey() + "')"); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + builder = addIfMatchHeader(builder, entity.getEtag()); + + builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); + + ClientResponse response = builder.put(ClientResponse.class); + ThrowIfError(response); + + UpdateEntityResult result = new UpdateEntityResult(); + result.setEtag(response.getHeaders().getFirst("ETag")); + + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java index de13eee4740fe..ce1d6dff3933c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -5,8 +5,18 @@ import java.util.Map; public class Entity { + private String etag; private Map properties = new HashMap(); + public String getEtag() { + return etag; + } + + public Entity setEtag(String etag) { + this.etag = etag; + return this; + } + public String getPartitionKey() { Property p = getProperty("PartitionKey"); return p == null ? null : (String) p.getValue(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java new file mode 100644 index 0000000000000..2db782e636934 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.models; + +public class UpdateEntityResult { + private String etag; + + public String getEtag() { + return etag; + } + + public void setEtag(String etag) { + this.etag = etag; + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index f07d3a1bb4685..254678ed9088a 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -288,6 +288,7 @@ public void insertEntityWorks() throws Exception { assertEquals("001", result.getEntity().getPartitionKey()); assertEquals("002", result.getEntity().getRowKey()); assertNotNull(result.getEntity().getTimestamp()); + assertNotNull(result.getEntity().getEtag()); assertNotNull(result.getEntity().getProperty("test")); assertEquals(true, result.getEntity().getProperty("test").getValue()); @@ -304,4 +305,26 @@ public void insertEntityWorks() throws Exception { assertNotNull(result.getEntity().getProperty("test5")); assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); } + + @Test + public void updateEntityWorks() throws Exception { + System.out.println("updateEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + InsertEntityResult result = service.insertEntity( + TEST_TABLE_2, + new Entity().setPartitionKey("001").setRowKey("updateEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date())); + + result.getEntity().setProperty("test4", EdmType.INT32, 5); + service.updateEntity(TEST_TABLE_2, result.getEntity()); + + // Assert + } } From 6696e2c75d249c8b934a1ea767193f0f283fe6c8 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 14:41:15 -0800 Subject: [PATCH 08/76] Add support for "DeleteEntity" --- .../services/table/TableContract.java | 6 +++ .../TableExceptionProcessor.java | 28 ++++++++++++ .../table/implementation/TableRestProxy.java | 25 ++++++++++- .../table/models/DeleteEntityOptions.java | 21 +++++++++ .../table/TableServiceIntegrationTest.java | 44 +++++++++++++++++++ 5 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 4b1611c61e86e..c3a2b2724aefc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -16,6 +16,7 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; @@ -63,4 +64,9 @@ public interface TableContract extends FilterableService { UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException; UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + + void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException; + + void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) + throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 8737038654b6a..a13a871752853 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -23,6 +23,7 @@ import com.microsoft.windowsazure.services.core.ServiceFilter; import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; @@ -295,4 +296,31 @@ public UpdateEntityResult updateEntity(String table, Entity entity, TableService throw processCatch(new ServiceException(e)); } } + + @Override + public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException { + try { + service.deleteEntity(table, partitionKey, rowKey); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) + throws ServiceException { + try { + service.deleteEntity(table, partitionKey, rowKey, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 852545c8b7752..e54e867a89a7f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -34,6 +34,7 @@ import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.BinaryFilterExpression; import com.microsoft.windowsazure.services.table.models.ConstantFilterExpression; +import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.FilterExpression; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; @@ -118,6 +119,10 @@ private List encodeODataURIValues(List values) { return list; } + private String getEntityPath(String table, String partitionKey, String rowKey) { + return table + "(" + "PartitionKey='" + partitionKey + "',RowKey='" + rowKey + "')"; + } + private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } @@ -384,7 +389,7 @@ public UpdateEntityResult updateEntity(String table, Entity entity) throws Servi public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException { WebResource webResource = getResource(options).path( - table + "(" + "PartitionKey='" + entity.getPartitionKey() + "',RowKey='" + entity.getRowKey() + "')"); + getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())); WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); @@ -400,4 +405,22 @@ public UpdateEntityResult updateEntity(String table, Entity entity, TableService return result; } + + @Override + public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException { + deleteEntity(table, partitionKey, rowKey, new DeleteEntityOptions()); + } + + @Override + public void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) + throws ServiceException { + WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey)); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + builder = addIfMatchHeader(builder, options.getEtag()); + + ClientResponse response = builder.delete(ClientResponse.class); + ThrowIfError(response); + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java new file mode 100644 index 0000000000000..441f3020822b4 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java @@ -0,0 +1,21 @@ +package com.microsoft.windowsazure.services.table.models; + +public class DeleteEntityOptions extends TableServiceOptions { + private String etag; + + @Override + public DeleteEntityOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public String getEtag() { + return etag; + } + + public DeleteEntityOptions setEtag(String etag) { + this.etag = etag; + return this; + } + +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 254678ed9088a..98e6346cf89f8 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetTableResult; @@ -327,4 +328,47 @@ public void updateEntityWorks() throws Exception { // Assert } + + @Test + public void deleteEntityWorks() throws Exception { + System.out.println("deleteEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + InsertEntityResult result = service.insertEntity( + TEST_TABLE_2, + new Entity().setPartitionKey("001").setRowKey("deleteEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date())); + + service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey()); + + // Assert + } + + @Test + public void deleteEntityWithETagWorks() throws Exception { + System.out.println("deleteEntityWithETagWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + InsertEntityResult result = service.insertEntity( + TEST_TABLE_2, + new Entity().setPartitionKey("001").setRowKey("deleteEntityWithETagWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date())); + + service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey(), + new DeleteEntityOptions().setEtag(result.getEntity().getEtag())); + + // Assert + } } From 9cede50c1450d309629c9638e4c6d1f962cecf6f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 14:58:07 -0800 Subject: [PATCH 09/76] Add support for "mergeEntity" --- .../services/table/TableContract.java | 4 +++ .../TableExceptionProcessor.java | 27 +++++++++++++++++++ .../table/implementation/TableRestProxy.java | 18 ++++++++++++- .../table/TableServiceIntegrationTest.java | 23 ++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index c3a2b2724aefc..7e5d968d08749 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -65,6 +65,10 @@ public interface TableContract extends FilterableService { UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException; + + UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException; void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index a13a871752853..abde4ec8cbe51 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -297,6 +297,33 @@ public UpdateEntityResult updateEntity(String table, Entity entity, TableService } } + @Override + public UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException { + try { + return service.mergeEntity(table, entity); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + try { + return service.mergeEntity(table, entity, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + @Override public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException { try { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index e54e867a89a7f..30178256e6912 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -388,6 +388,22 @@ public UpdateEntityResult updateEntity(String table, Entity entity) throws Servi @Override public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException { + return updateOrMergeEntityCore(table, entity, "PUT", options); + } + + @Override + public UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException { + return updateEntity(table, entity, new TableServiceOptions()); + } + + @Override + public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + return updateOrMergeEntityCore(table, entity, "MERGE", options); + } + + private UpdateEntityResult updateOrMergeEntityCore(String table, Entity entity, String verb, + TableServiceOptions options) throws ServiceException { WebResource webResource = getResource(options).path( getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())); @@ -397,7 +413,7 @@ public UpdateEntityResult updateEntity(String table, Entity entity, TableService builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); - ClientResponse response = builder.put(ClientResponse.class); + ClientResponse response = builder.method(verb, ClientResponse.class); ThrowIfError(response); UpdateEntityResult result = new UpdateEntityResult(); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 98e6346cf89f8..f5ab25bf9e37c 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -329,6 +329,29 @@ public void updateEntityWorks() throws Exception { // Assert } + @Test + public void mergeEntityWorks() throws Exception { + System.out.println("mergeEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + InsertEntityResult result = service.insertEntity( + TEST_TABLE_2, + new Entity().setPartitionKey("001").setRowKey("mergeEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date())); + + result.getEntity().setProperty("test4", EdmType.INT32, 5); + result.getEntity().setProperty("test6", EdmType.INT32, 6); + service.mergeEntity(TEST_TABLE_2, result.getEntity()); + + // Assert + } + @Test public void deleteEntityWorks() throws Exception { System.out.println("deleteEntityWorks()"); From b9a00d4b7ae96a152cdbbf0b9af166d0e8cd5007 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 15:13:34 -0800 Subject: [PATCH 10/76] Add support for "insertOrReplaceEntity" --- .../services/table/TableContract.java | 5 ++++ .../TableExceptionProcessor.java | 27 +++++++++++++++++++ .../table/implementation/TableRestProxy.java | 21 ++++++++++++--- .../table/TableServiceIntegrationTest.java | 22 +++++++++++++++ 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 7e5d968d08749..88526099a933e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -69,6 +69,11 @@ public interface TableContract extends FilterableService { UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException; + + UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException; + void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException; void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index abde4ec8cbe51..df7384900baf4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -324,6 +324,33 @@ public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceO } } + @Override + public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException { + try { + return service.insertOrReplaceEntity(table, entity); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + try { + return service.insertOrReplaceEntity(table, entity, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + @Override public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException { try { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 30178256e6912..db969f5c182a5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -388,7 +388,7 @@ public UpdateEntityResult updateEntity(String table, Entity entity) throws Servi @Override public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException { - return updateOrMergeEntityCore(table, entity, "PUT", options); + return putOrMergeEntityCore(table, entity, "PUT", true/*includeEtag*/, options); } @Override @@ -399,17 +399,30 @@ public UpdateEntityResult mergeEntity(String table, Entity entity) throws Servic @Override public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException { - return updateOrMergeEntityCore(table, entity, "MERGE", options); + return putOrMergeEntityCore(table, entity, "MERGE", true/*includeEtag*/, options); } - private UpdateEntityResult updateOrMergeEntityCore(String table, Entity entity, String verb, + @Override + public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException { + return insertOrReplaceEntity(table, entity, new TableServiceOptions()); + } + + @Override + public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + return putOrMergeEntityCore(table, entity, "PUT", false/*includeEtag*/, options); + } + + private UpdateEntityResult putOrMergeEntityCore(String table, Entity entity, String verb, boolean includeEtag, TableServiceOptions options) throws ServiceException { WebResource webResource = getResource(options).path( getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())); WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); - builder = addIfMatchHeader(builder, entity.getEtag()); + if (includeEtag) { + builder = addIfMatchHeader(builder, entity.getEtag()); + } builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index f5ab25bf9e37c..5962a2b35bc8e 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -329,6 +329,28 @@ public void updateEntityWorks() throws Exception { // Assert } + @Test + public void insertOrReplaceEntityWorks() throws Exception { + System.out.println("insertOrReplaceEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrReplaceEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + service.insertOrReplaceEntity(TEST_TABLE_2, entity); + entity.setProperty("test4", EdmType.INT32, 5); + entity.setProperty("test6", EdmType.INT32, 6); + service.insertOrReplaceEntity(TEST_TABLE_2, entity); + + // Assert + } + @Test public void mergeEntityWorks() throws Exception { System.out.println("mergeEntityWorks()"); From a4f49b043243a7fd73c1dff97a61db7ee09f1708 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 15:18:05 -0800 Subject: [PATCH 11/76] Add support for "insertOrMergeEntity" --- .../services/table/TableContract.java | 5 ++++ .../TableExceptionProcessor.java | 27 +++++++++++++++++++ .../table/implementation/TableRestProxy.java | 11 ++++++++ .../table/TableServiceIntegrationTest.java | 22 +++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 88526099a933e..412c9afbdea50 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -74,6 +74,11 @@ public interface TableContract extends FilterableService { UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException; + + UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException; + void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException; void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index df7384900baf4..fc1ac6473d7de 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -351,6 +351,33 @@ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, Tab } } + @Override + public UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException { + try { + return service.insertOrMergeEntity(table, entity); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + try { + return service.insertOrMergeEntity(table, entity, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + @Override public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException { try { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index db969f5c182a5..603a0c04f1d5a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -413,6 +413,17 @@ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, Tab return putOrMergeEntityCore(table, entity, "PUT", false/*includeEtag*/, options); } + @Override + public UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException { + return insertOrReplaceEntity(table, entity, new TableServiceOptions()); + } + + @Override + public UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options) + throws ServiceException { + return putOrMergeEntityCore(table, entity, "MERGE", false/*includeEtag*/, options); + } + private UpdateEntityResult putOrMergeEntityCore(String table, Entity entity, String verb, boolean includeEtag, TableServiceOptions options) throws ServiceException { WebResource webResource = getResource(options).path( diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 5962a2b35bc8e..35bac81f30cc4 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -351,6 +351,28 @@ public void insertOrReplaceEntityWorks() throws Exception { // Assert } + @Test + public void insertOrMergeEntityWorks() throws Exception { + System.out.println("insertOrMergeEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrMergeEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + service.insertOrMergeEntity(TEST_TABLE_2, entity); + entity.setProperty("test4", EdmType.INT32, 5); + entity.setProperty("test6", EdmType.INT32, 6); + service.insertOrMergeEntity(TEST_TABLE_2, entity); + + // Assert + } + @Test public void mergeEntityWorks() throws Exception { System.out.println("mergeEntityWorks()"); From bf53b9c4e03e2ccac31dda10e13bcae846e9300f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 15:51:07 -0800 Subject: [PATCH 12/76] Add support for "getEntity" and "queryEntities" --- .../services/table/TableContract.java | 12 ++ .../implementation/AtomReaderWriter.java | 52 +++++- .../TableExceptionProcessor.java | 56 +++++++ .../table/implementation/TableRestProxy.java | 49 ++++++ .../table/models/GetEntityResult.java | 13 ++ .../table/models/QueryEntitiesOptions.java | 20 +++ .../table/models/QueryEntitiesResult.java | 34 ++++ .../table/TableServiceIntegrationTest.java | 156 +++++++++++++----- 8 files changed, 345 insertions(+), 47 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 412c9afbdea50..c214333e8a634 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -18,10 +18,13 @@ import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; +import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -83,4 +86,13 @@ UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableService void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) throws ServiceException; + + GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException; + + GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options) + throws ServiceException; + + QueryEntitiesResult queryEntities(String table) throws ServiceException; + + QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index 0f85fcede8cb1..c5803bfb54111 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -117,26 +117,40 @@ public TableEntry parseTableEntry(InputStream stream) { } } - public Entity parseEntityEntry(InputStream stream) { + public List parseEntityEntries(InputStream stream) { try { XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); - Entity result = new Entity(); expect(xmlr, XMLStreamConstants.START_DOCUMENT); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed"); - result.setEtag(xmlr.getAttributeValue(null, "etag")); - expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); - - while (!isEndElement(xmlr, "entry")) { - if (isStartElement(xmlr, "properties")) { - result.setProperties(parseEntryProperties(xmlr)); + List result = new ArrayList(); + while (!isEndElement(xmlr, "feed")) { + // Process "entry" elements only + if (isStartElement(xmlr, "entry")) { + result.add(parseEntityEntry(xmlr)); } else { nextSignificant(xmlr); } } - expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry"); + expect(xmlr, XMLStreamConstants.END_ELEMENT, "feed"); + expect(xmlr, XMLStreamConstants.END_DOCUMENT); + + return result; + } + catch (XMLStreamException e) { + throw new RuntimeException(e); + } + } + + public Entity parseEntityEntry(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + Entity result = parseEntityEntry(xmlr); expect(xmlr, XMLStreamConstants.END_DOCUMENT); return result; @@ -218,6 +232,26 @@ private TableEntry parseTableEntry(XMLStreamReader xmlr) throws XMLStreamExcepti return result; } + private Entity parseEntityEntry(XMLStreamReader xmlr) throws XMLStreamException { + Entity result = new Entity(); + + result.setEtag(xmlr.getAttributeValue(null, "etag")); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); + + while (!isEndElement(xmlr, "entry")) { + if (isStartElement(xmlr, "properties")) { + result.setProperties(parseEntryProperties(xmlr)); + } + else { + nextSignificant(xmlr); + } + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry"); + + return result; + } + private Map parseEntryProperties(XMLStreamReader xmlr) throws XMLStreamException { Map result = new HashMap(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index fc1ac6473d7de..93a99570fdbaf 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -25,10 +25,13 @@ import com.microsoft.windowsazure.services.table.TableContract; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; +import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -404,4 +407,57 @@ public void deleteEntity(String table, String partitionKey, String rowKey, Delet throw processCatch(new ServiceException(e)); } } + + @Override + public GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException { + try { + return service.getEntity(table, partitionKey, rowKey); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options) + throws ServiceException { + try { + return service.getEntity(table, partitionKey, rowKey, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public QueryEntitiesResult queryEntities(String table) throws ServiceException { + try { + return service.queryEntities(table); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException { + try { + return service.queryEntities(table, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 603a0c04f1d5a..51fc33bbeddd1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -37,12 +37,15 @@ import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.FilterExpression; +import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.LitteralFilterExpression; import com.microsoft.windowsazure.services.table.models.QueryBuilder; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -463,4 +466,50 @@ public void deleteEntity(String table, String partitionKey, String rowKey, Delet ClientResponse response = builder.delete(ClientResponse.class); ThrowIfError(response); } + + @Override + public GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException { + return getEntity(table, partitionKey, rowKey, new TableServiceOptions()); + } + + @Override + public GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options) + throws ServiceException { + WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey)); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + ClientResponse response = builder.get(ClientResponse.class); + ThrowIfError(response); + + GetEntityResult result = new GetEntityResult(); + result.setEntity(atomReaderWriter.parseEntityEntry(response.getEntityInputStream())); + + return result; + } + + @Override + public QueryEntitiesResult queryEntities(String table) throws ServiceException { + return queryEntities(table, new QueryEntitiesOptions()); + } + + @Override + public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException { + WebResource webResource = getResource(options).path(table); + webResource = addOptionalQuery(webResource, options.getQuery()); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + ClientResponse response = builder.get(ClientResponse.class); + ThrowIfError(response); + + QueryEntitiesResult result = new QueryEntitiesResult(); + result.setNextPartitionKey(response.getHeaders().getFirst("x-ms-continuation-NextPartitionKey")); + result.setNextRowKey(response.getHeaders().getFirst("x-ms-continuation-NextRowKey")); + result.setEntities(atomReaderWriter.parseEntityEntries(response.getEntityInputStream())); + + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java new file mode 100644 index 0000000000000..34195d4795047 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java @@ -0,0 +1,13 @@ +package com.microsoft.windowsazure.services.table.models; + +public class GetEntityResult { + private Entity entity; + + public Entity getEntity() { + return entity; + } + + public void setEntity(Entity entity) { + this.entity = entity; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java new file mode 100644 index 0000000000000..55652baec98d9 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -0,0 +1,20 @@ +package com.microsoft.windowsazure.services.table.models; + +public class QueryEntitiesOptions extends TableServiceOptions { + private QueryBuilder query; + + @Override + public QueryEntitiesOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public QueryBuilder getQuery() { + return query; + } + + public QueryEntitiesOptions setQuery(QueryBuilder query) { + this.query = query; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java new file mode 100644 index 0000000000000..3977ccc9b97d4 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java @@ -0,0 +1,34 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.ArrayList; +import java.util.List; + +public class QueryEntitiesResult { + private String nextPartitionKey; + private String nextRowKey; + private List entities = new ArrayList(); + + public List getEntities() { + return entities; + } + + public void setEntities(List entities) { + this.entities = entities; + } + + public String getNextPartitionKey() { + return nextPartitionKey; + } + + public void setNextPartitionKey(String nextPartitionKey) { + this.nextPartitionKey = nextPartitionKey; + } + + public String getNextRowKey() { + return nextRowKey; + } + + public void setNextRowKey(String nextRowKey) { + this.nextRowKey = nextRowKey; + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 35bac81f30cc4..27420b286d082 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -28,9 +28,11 @@ import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; +import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableEntry; @@ -40,7 +42,7 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static final String createableTablesPrefix = "csdktest"; private static String TEST_TABLE_1; private static String TEST_TABLE_2; - //private static String TEST_TABLE_3; + private static String TEST_TABLE_3; //private static String TEST_TABLE_4; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; @@ -64,7 +66,7 @@ public static void setup() throws Exception { TEST_TABLE_1 = testTables[0]; TEST_TABLE_2 = testTables[1]; - //TEST_TABLE_3 = testTables[2]; + TEST_TABLE_3 = testTables[2]; //TEST_TABLE_4 = testTables[3]; CREATABLE_TABLE_1 = creatableTables[0]; @@ -273,21 +275,20 @@ public void insertEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("insertEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); // Act - InsertEntityResult result = service.insertEntity( - TEST_TABLE_2, - new Entity().setPartitionKey("001").setRowKey("002").setProperty("test", EdmType.BOOLEAN, true) - .setProperty("test2", EdmType.STRING, "value").setProperty("test3", EdmType.INT32, 3) - .setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date())); + InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); // Assert assertNotNull(result); assertNotNull(result.getEntity()); assertEquals("001", result.getEntity().getPartitionKey()); - assertEquals("002", result.getEntity().getRowKey()); + assertEquals("insertEntityWorks", result.getEntity().getRowKey()); assertNotNull(result.getEntity().getTimestamp()); assertNotNull(result.getEntity().getEtag()); @@ -314,15 +315,13 @@ public void updateEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("updateEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); // Act - InsertEntityResult result = service.insertEntity( - TEST_TABLE_2, - new Entity().setPartitionKey("001").setRowKey("updateEntityWorks") - .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") - .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date())); - + InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); result.getEntity().setProperty("test4", EdmType.INT32, 5); service.updateEntity(TEST_TABLE_2, result.getEntity()); @@ -336,13 +335,12 @@ public void insertOrReplaceEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); - - // Act Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrReplaceEntityWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) .setProperty("test5", EdmType.DATETIME, new Date()); + // Act service.insertOrReplaceEntity(TEST_TABLE_2, entity); entity.setProperty("test4", EdmType.INT32, 5); entity.setProperty("test6", EdmType.INT32, 6); @@ -358,13 +356,12 @@ public void insertOrMergeEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); - - // Act Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrMergeEntityWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) .setProperty("test5", EdmType.DATETIME, new Date()); + // Act service.insertOrMergeEntity(TEST_TABLE_2, entity); entity.setProperty("test4", EdmType.INT32, 5); entity.setProperty("test6", EdmType.INT32, 6); @@ -380,14 +377,13 @@ public void mergeEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("mergeEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); // Act - InsertEntityResult result = service.insertEntity( - TEST_TABLE_2, - new Entity().setPartitionKey("001").setRowKey("mergeEntityWorks") - .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") - .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date())); + InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); result.getEntity().setProperty("test4", EdmType.INT32, 5); result.getEntity().setProperty("test6", EdmType.INT32, 6); @@ -403,14 +399,13 @@ public void deleteEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("deleteEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); // Act - InsertEntityResult result = service.insertEntity( - TEST_TABLE_2, - new Entity().setPartitionKey("001").setRowKey("deleteEntityWorks") - .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") - .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date())); + InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey()); @@ -424,18 +419,103 @@ public void deleteEntityWithETagWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("deleteEntityWithETagWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); // Act - InsertEntityResult result = service.insertEntity( - TEST_TABLE_2, - new Entity().setPartitionKey("001").setRowKey("deleteEntityWithETagWorks") - .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") - .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date())); + InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey(), new DeleteEntityOptions().setEtag(result.getEntity().getEtag())); // Assert } + + @Test + public void getEntityWorks() throws Exception { + System.out.println("getEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("getEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + // Act + InsertEntityResult insertResult = service.insertEntity(TEST_TABLE_2, entity); + GetEntityResult result = service.getEntity(TEST_TABLE_2, insertResult.getEntity().getPartitionKey(), + insertResult.getEntity().getRowKey()); + + // Assert + assertNotNull(result); + assertNotNull(result.getEntity()); + + assertEquals("001", result.getEntity().getPartitionKey()); + assertEquals("getEntityWorks", result.getEntity().getRowKey()); + assertNotNull(result.getEntity().getTimestamp()); + assertNotNull(result.getEntity().getEtag()); + + assertNotNull(result.getEntity().getProperty("test")); + assertEquals(true, result.getEntity().getProperty("test").getValue()); + + assertNotNull(result.getEntity().getProperty("test2")); + assertEquals("value", result.getEntity().getProperty("test2").getValue()); + + assertNotNull(result.getEntity().getProperty("test3")); + assertEquals(3, result.getEntity().getProperty("test3").getValue()); + + assertNotNull(result.getEntity().getProperty("test4")); + assertEquals(12345678901L, result.getEntity().getProperty("test4").getValue()); + + assertNotNull(result.getEntity().getProperty("test5")); + assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); + } + + @Test + public void queryEntityWorks() throws Exception { + System.out.println("queryEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntityWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + // Act + service.insertEntity(TEST_TABLE_3, entity); + QueryEntitiesResult result = service.queryEntities(TEST_TABLE_3); + + // Assert + assertNotNull(result); + assertNotNull(result.getEntities()); + assertEquals(1, result.getEntities().size()); + + assertNotNull(result.getEntities().get(0)); + + assertEquals("001", result.getEntities().get(0).getPartitionKey()); + assertEquals("queryEntityWorks", result.getEntities().get(0).getRowKey()); + assertNotNull(result.getEntities().get(0).getTimestamp()); + assertNotNull(result.getEntities().get(0).getEtag()); + + assertNotNull(result.getEntities().get(0).getProperty("test")); + assertEquals(true, result.getEntities().get(0).getProperty("test").getValue()); + + assertNotNull(result.getEntities().get(0).getProperty("test2")); + assertEquals("value", result.getEntities().get(0).getProperty("test2").getValue()); + + assertNotNull(result.getEntities().get(0).getProperty("test3")); + assertEquals(3, result.getEntities().get(0).getProperty("test3").getValue()); + + assertNotNull(result.getEntities().get(0).getProperty("test4")); + assertEquals(12345678901L, result.getEntities().get(0).getProperty("test4").getValue()); + + assertNotNull(result.getEntities().get(0).getProperty("test5")); + assertTrue(result.getEntities().get(0).getProperty("test5").getValue() instanceof Date); + } } From a72bc2ad684f9a18d9cbc62ba0d4446497545877 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 16:29:43 -0800 Subject: [PATCH 13/76] Rename a few classes, fix support for query continuation --- .../table/implementation/TableRestProxy.java | 72 +++++++------- .../services/table/models/BinaryFilter.java | 34 +++++++ .../table/models/BinaryFilterExpression.java | 34 ------- ...terExpression.java => ConstantFilter.java} | 4 +- .../services/table/models/Filter.java | 47 +++++++++ .../table/models/FilterExpression.java | 47 --------- ...terExpression.java => LitteralFilter.java} | 4 +- .../services/table/models/Query.java | 67 +++++++++++++ .../services/table/models/QueryBuilder.java | 96 ------------------- .../table/models/QueryEntitiesOptions.java | 26 ++++- .../table/models/QueryTablesOptions.java | 16 +++- .../table/models/QueryTablesResult.java | 10 +- ...FilterExpression.java => UnaryFilter.java} | 10 +- 13 files changed, 230 insertions(+), 237 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/{ConstantFilterExpression.java => ConstantFilter.java} (61%) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/{LitteralFilterExpression.java => LitteralFilter.java} (62%) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/{UnaryFilterExpression.java => UnaryFilter.java} (52%) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 51fc33bbeddd1..95558d7021dd3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -32,25 +32,25 @@ import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; import com.microsoft.windowsazure.services.table.TableConfiguration; import com.microsoft.windowsazure.services.table.TableContract; -import com.microsoft.windowsazure.services.table.models.BinaryFilterExpression; -import com.microsoft.windowsazure.services.table.models.ConstantFilterExpression; +import com.microsoft.windowsazure.services.table.models.BinaryFilter; +import com.microsoft.windowsazure.services.table.models.ConstantFilter; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; -import com.microsoft.windowsazure.services.table.models.FilterExpression; +import com.microsoft.windowsazure.services.table.models.Filter; import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; -import com.microsoft.windowsazure.services.table.models.LitteralFilterExpression; -import com.microsoft.windowsazure.services.table.models.QueryBuilder; +import com.microsoft.windowsazure.services.table.models.LitteralFilter; +import com.microsoft.windowsazure.services.table.models.Query; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; -import com.microsoft.windowsazure.services.table.models.UnaryFilterExpression; +import com.microsoft.windowsazure.services.table.models.UnaryFilter; import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; @@ -130,13 +130,13 @@ private WebResource addOptionalQueryParam(WebResource webResource, String key, O return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } - private WebResource addOptionalQuery(WebResource webResource, QueryBuilder query) { + private WebResource addOptionalQuery(WebResource webResource, Query query) { if (query == null) return webResource; - if (query.getFields() != null && query.getFields().size() > 0) { + if (query.getSelectFields() != null && query.getSelectFields().size() > 0) { webResource = addOptionalQueryParam(webResource, "$select", - CommaStringBuilder.join(encodeODataURIValues(query.getFields()))); + CommaStringBuilder.join(encodeODataURIValues(query.getSelectFields()))); } if (query.getTop() != null) { @@ -147,54 +147,45 @@ private WebResource addOptionalQuery(WebResource webResource, QueryBuilder query webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(query.getFilter())); } - if (query.getOrderBy() != null) { + if (query.getOrderByFields() != null) { webResource = addOptionalQueryParam(webResource, "$orderby", - CommaStringBuilder.join(encodeODataURIValues(query.getOrderBy()))); - } - - if (query.getNextPartitionKey() != null) { - webResource = addOptionalQueryParam(webResource, "NextPartitionKey", - encodeODataURIValue(query.getNextPartitionKey())); - } - - if (query.getNextRowKey() != null) { - webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(query.getNextRowKey())); + CommaStringBuilder.join(encodeODataURIValues(query.getOrderByFields()))); } return webResource; } - private String buildFilterExpression(FilterExpression filter) { + private String buildFilterExpression(Filter filter) { StringBuilder sb = new StringBuilder(); buildFilterExpression(filter, sb); return sb.toString(); } - private void buildFilterExpression(FilterExpression filter, StringBuilder sb) { + private void buildFilterExpression(Filter filter, StringBuilder sb) { if (filter == null) return; - if (filter instanceof LitteralFilterExpression) { - sb.append(((LitteralFilterExpression) filter).getLitteral()); + if (filter instanceof LitteralFilter) { + sb.append(((LitteralFilter) filter).getLitteral()); } - else if (filter instanceof ConstantFilterExpression) { + else if (filter instanceof ConstantFilter) { sb.append("'"); - sb.append(((ConstantFilterExpression) filter).getValue()); + sb.append(((ConstantFilter) filter).getValue()); sb.append("'"); } - else if (filter instanceof UnaryFilterExpression) { - sb.append(((UnaryFilterExpression) filter).getOperator()); + else if (filter instanceof UnaryFilter) { + sb.append(((UnaryFilter) filter).getOperator()); sb.append("("); - buildFilterExpression(((UnaryFilterExpression) filter).getOperand(), sb); + buildFilterExpression(((UnaryFilter) filter).getOperand(), sb); sb.append(")"); } - else if (filter instanceof BinaryFilterExpression) { + else if (filter instanceof BinaryFilter) { sb.append("("); - buildFilterExpression(((BinaryFilterExpression) filter).getLeft(), sb); + buildFilterExpression(((BinaryFilter) filter).getLeft(), sb); sb.append(" "); - sb.append(((BinaryFilterExpression) filter).getOperator()); + sb.append(((BinaryFilter) filter).getOperator()); sb.append(" "); - buildFilterExpression(((BinaryFilterExpression) filter).getRight(), sb); + buildFilterExpression(((BinaryFilter) filter).getRight(), sb); sb.append(")"); } } @@ -288,15 +279,12 @@ public QueryTablesResult listTables() throws ServiceException { @Override public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> uppperBound is prefix + '{' - FilterExpression filter = FilterExpression.and( - FilterExpression.ge(FilterExpression.litteral("TableName"), - FilterExpression.constant(options.getPrefix())), - FilterExpression.le(FilterExpression.litteral("TableName"), - FilterExpression.constant(options.getPrefix() + "{"))); + Filter filter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(options.getPrefix())), + Filter.le(Filter.litteral("TableName"), Filter.constant(options.getPrefix() + "{"))); QueryTablesOptions queryTableOptions = new QueryTablesOptions(); queryTableOptions.setTimeout(options.getTimeout()); - queryTableOptions.setQuery(new QueryBuilder().setFilter(filter)); + queryTableOptions.setQuery(new Query().setFilter(filter)); return queryTables(queryTableOptions); } @@ -309,6 +297,7 @@ public QueryTablesResult queryTables() throws ServiceException { public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { WebResource webResource = getResource(options).path("Tables"); webResource = addOptionalQuery(webResource, options.getQuery()); + webResource = addOptionalQueryParam(webResource, "NextTableName", options.getNextTableName()); WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); @@ -317,7 +306,7 @@ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceE ThrowIfError(response); QueryTablesResult result = new QueryTablesResult(); - result.setContinuationToken(response.getHeaders().getFirst("x-ms-continuation-NextTableName")); + result.setNextTableName(response.getHeaders().getFirst("x-ms-continuation-NextTableName")); result.setTables(atomReaderWriter.parseTableEntries(response.getEntityInputStream())); return result; @@ -498,6 +487,9 @@ public QueryEntitiesResult queryEntities(String table) throws ServiceException { public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException { WebResource webResource = getResource(options).path(table); webResource = addOptionalQuery(webResource, options.getQuery()); + webResource = addOptionalQueryParam(webResource, "NextPartitionKey", + encodeODataURIValue(options.getNextPartitionKey())); + webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(options.getNextRowKey())); WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java new file mode 100644 index 0000000000000..462be9a865c63 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java @@ -0,0 +1,34 @@ +package com.microsoft.windowsazure.services.table.models; + +public class BinaryFilter extends Filter { + private String operator; + private Filter left; + private Filter right; + + public String getOperator() { + return operator; + } + + public BinaryFilter setOperator(String operator) { + this.operator = operator; + return this; + } + + public Filter getLeft() { + return left; + } + + public BinaryFilter setLeft(Filter left) { + this.left = left; + return this; + } + + public Filter getRight() { + return right; + } + + public BinaryFilter setRight(Filter right) { + this.right = right; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java deleted file mode 100644 index 25547090206ad..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilterExpression.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.microsoft.windowsazure.services.table.models; - -public class BinaryFilterExpression extends FilterExpression { - private String operator; - private FilterExpression left; - private FilterExpression right; - - public String getOperator() { - return operator; - } - - public BinaryFilterExpression setOperator(String operator) { - this.operator = operator; - return this; - } - - public FilterExpression getLeft() { - return left; - } - - public BinaryFilterExpression setLeft(FilterExpression left) { - this.left = left; - return this; - } - - public FilterExpression getRight() { - return right; - } - - public BinaryFilterExpression setRight(FilterExpression right) { - this.right = right; - return this; - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java similarity index 61% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java index 4c38168ca2955..9dd16b1a1daa8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilterExpression.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -1,13 +1,13 @@ package com.microsoft.windowsazure.services.table.models; -public class ConstantFilterExpression extends FilterExpression { +public class ConstantFilter extends Filter { private Object value; public Object getValue() { return value; } - public ConstantFilterExpression setValue(Object value) { + public ConstantFilter setValue(Object value) { this.value = value; return this; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java new file mode 100644 index 0000000000000..fcb3560758a2e --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -0,0 +1,47 @@ +package com.microsoft.windowsazure.services.table.models; + +public class Filter { + public static UnaryFilter not(Filter operand) { + return new UnaryFilter().setOperator("not").setOperand(operand); + } + + public static BinaryFilter and(Filter left, Filter right) { + return new BinaryFilter().setOperator("and").setLeft(left).setRight(right); + } + + public static BinaryFilter or(Filter left, Filter right) { + return new BinaryFilter().setOperator("or").setLeft(left).setRight(right); + } + + public static BinaryFilter eq(Filter left, Filter right) { + return new BinaryFilter().setOperator("eq").setLeft(left).setRight(right); + } + + public static BinaryFilter ne(Filter left, Filter right) { + return new BinaryFilter().setOperator("ne").setLeft(left).setRight(right); + } + + public static BinaryFilter ge(Filter left, Filter right) { + return new BinaryFilter().setOperator("ge").setLeft(left).setRight(right); + } + + public static BinaryFilter gt(Filter left, Filter right) { + return new BinaryFilter().setOperator("gt").setLeft(left).setRight(right); + } + + public static BinaryFilter lt(Filter left, Filter right) { + return new BinaryFilter().setOperator("lt").setLeft(left).setRight(right); + } + + public static BinaryFilter le(Filter left, Filter right) { + return new BinaryFilter().setOperator("le").setLeft(left).setRight(right); + } + + public static ConstantFilter constant(Object value) { + return new ConstantFilter().setValue(value); + } + + public static LitteralFilter litteral(String value) { + return new LitteralFilter().setLitteral(value); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java deleted file mode 100644 index 655a53838e755..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/FilterExpression.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.microsoft.windowsazure.services.table.models; - -public class FilterExpression { - public static UnaryFilterExpression not(FilterExpression operand) { - return new UnaryFilterExpression().setOperator("not").setOperand(operand); - } - - public static BinaryFilterExpression and(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("and").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression or(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("or").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression eq(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("eq").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression ne(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("ne").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression ge(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("ge").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression gt(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("gt").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression lt(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("lt").setLeft(left).setRight(right); - } - - public static BinaryFilterExpression le(FilterExpression left, FilterExpression right) { - return new BinaryFilterExpression().setOperator("le").setLeft(left).setRight(right); - } - - public static ConstantFilterExpression constant(Object value) { - return new ConstantFilterExpression().setValue(value); - } - - public static LitteralFilterExpression litteral(String value) { - return new LitteralFilterExpression().setLitteral(value); - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java similarity index 62% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java index 8c4d857624e3b..d67f47162c4d6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilterExpression.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java @@ -1,13 +1,13 @@ package com.microsoft.windowsazure.services.table.models; -public class LitteralFilterExpression extends FilterExpression { +public class LitteralFilter extends Filter { private String litteral; public String getLitteral() { return litteral; } - public LitteralFilterExpression setLitteral(String litteral) { + public LitteralFilter setLitteral(String litteral) { this.litteral = litteral; return this; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java new file mode 100644 index 0000000000000..57e7f8c30b660 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java @@ -0,0 +1,67 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.ArrayList; +import java.util.List; + +public class Query { + private List selectFields = new ArrayList(); + private String from; + private Filter filter; + private List orderByFields = new ArrayList(); + private Integer top; + + public List getSelectFields() { + return selectFields; + } + + public Query setSelectFields(List selectFields) { + this.selectFields = selectFields; + return this; + } + + public Query addSelectField(String selectField) { + this.selectFields.add(selectField); + return this; + } + + public String getFrom() { + return from; + } + + public Query setFrom(String from) { + this.from = from; + return this; + } + + public Filter getFilter() { + return filter; + } + + public Query setFilter(Filter filter) { + this.filter = filter; + return this; + } + + public List getOrderByFields() { + return orderByFields; + } + + public Query setOrderByFields(List orderByFields) { + this.orderByFields = orderByFields; + return this; + } + + public Query addOrderByField(String orderByField) { + this.orderByFields.add(orderByField); + return this; + } + + public Integer getTop() { + return top; + } + + public Query setTop(Integer top) { + this.top = top; + return this; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java deleted file mode 100644 index 7c915cf53cb49..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryBuilder.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.microsoft.windowsazure.services.table.models; - -import java.util.List; - -public class QueryBuilder { - private List fields; - private String from; - private FilterExpression filter; - private List orderBy; - private Integer top; - private String partitionKey; - private String nextPartitionKey; - private String rowKey; - private String nextRowKey; - - public List getFields() { - return fields; - } - - public QueryBuilder setFields(List fields) { - this.fields = fields; - return this; - } - - public String getFrom() { - return from; - } - - public QueryBuilder setFrom(String from) { - this.from = from; - return this; - } - - public FilterExpression getFilter() { - return filter; - } - - public QueryBuilder setFilter(FilterExpression where) { - this.filter = where; - return this; - } - - public List getOrderBy() { - return orderBy; - } - - public QueryBuilder setOrderBy(List orderBy) { - this.orderBy = orderBy; - return this; - } - - public Integer getTop() { - return top; - } - - public QueryBuilder setTop(Integer top) { - this.top = top; - return this; - } - - public String getPartitionKey() { - return partitionKey; - } - - public QueryBuilder setPartitionKey(String partitionKey) { - this.partitionKey = partitionKey; - return this; - } - - public String getNextPartitionKey() { - return nextPartitionKey; - } - - public QueryBuilder setNextPartitionKey(String nextPartitionKey) { - this.nextPartitionKey = nextPartitionKey; - return this; - } - - public String getRowKey() { - return rowKey; - } - - public QueryBuilder setRowKey(String rowKey) { - this.rowKey = rowKey; - return this; - } - - public String getNextRowKey() { - return nextRowKey; - } - - public QueryBuilder setNextRowKey(String nextRowKey) { - this.nextRowKey = nextRowKey; - return this; - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index 55652baec98d9..1de1cd47426b5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -1,7 +1,9 @@ package com.microsoft.windowsazure.services.table.models; public class QueryEntitiesOptions extends TableServiceOptions { - private QueryBuilder query; + private Query query; + public String nextPartitionKey; + public String nextRowKey; @Override public QueryEntitiesOptions setTimeout(Integer timeout) { @@ -9,12 +11,30 @@ public QueryEntitiesOptions setTimeout(Integer timeout) { return this; } - public QueryBuilder getQuery() { + public Query getQuery() { return query; } - public QueryEntitiesOptions setQuery(QueryBuilder query) { + public QueryEntitiesOptions setQuery(Query query) { this.query = query; return this; } + + public String getNextPartitionKey() { + return nextPartitionKey; + } + + public QueryEntitiesOptions setNextPartitionKey(String nextPartitionKey) { + this.nextPartitionKey = nextPartitionKey; + return this; + } + + public String getNextRowKey() { + return nextRowKey; + } + + public QueryEntitiesOptions setNextRowKey(String nextRowKey) { + this.nextRowKey = nextRowKey; + return this; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index d4896fa674988..d402cd8527cce 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -1,7 +1,8 @@ package com.microsoft.windowsazure.services.table.models; public class QueryTablesOptions extends TableServiceOptions { - private QueryBuilder query; + private String nextTableName; + private Query query; @Override public QueryTablesOptions setTimeout(Integer timeout) { @@ -9,12 +10,21 @@ public QueryTablesOptions setTimeout(Integer timeout) { return this; } - public QueryBuilder getQuery() { + public Query getQuery() { return query; } - public QueryTablesOptions setQuery(QueryBuilder query) { + public QueryTablesOptions setQuery(Query query) { this.query = query; return this; } + + public String getNextTableName() { + return nextTableName; + } + + public QueryTablesOptions setNextTableName(String nextTableName) { + this.nextTableName = nextTableName; + return this; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java index 3816ffe5899af..dfd1c1b7f4aae 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -3,15 +3,15 @@ import java.util.List; public class QueryTablesResult { - private String continuationToken; + private String nextTableName; private List tables; - public String getContinuationToken() { - return continuationToken; + public String getNextTableName() { + return nextTableName; } - public void setContinuationToken(String continuationToken) { - this.continuationToken = continuationToken; + public void setNextTableName(String nextTableName) { + this.nextTableName = nextTableName; } public List getTables() { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java similarity index 52% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java index 3e0efd984bffa..42d4b7a870fc4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilterExpression.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java @@ -1,23 +1,23 @@ package com.microsoft.windowsazure.services.table.models; -public class UnaryFilterExpression extends FilterExpression { +public class UnaryFilter extends Filter { private String operator; - private FilterExpression operand; + private Filter operand; public String getOperator() { return operator; } - public UnaryFilterExpression setOperator(String operator) { + public UnaryFilter setOperator(String operator) { this.operator = operator; return this; } - public FilterExpression getOperand() { + public Filter getOperand() { return operand; } - public UnaryFilterExpression setOperand(FilterExpression operand) { + public UnaryFilter setOperand(Filter operand) { this.operand = operand; return this; } From 711e23d2b66d96336403592fb609d5f531bfd60c Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 17:04:54 -0800 Subject: [PATCH 14/76] Fix bug with server sometimes sending back shorter ISO 8601 dates --- .../implementation/ISO8601DateConverter.java | 17 ++++++- .../ISO8601DateConverterTests.java | 51 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 08d7d6ea9ce8a..e796bf00794cc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -27,13 +27,22 @@ public class ISO8601DateConverter { // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"; + private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; public String format(Date date) { return getFormat().format(date); } public Date parse(String date) throws ParseException { - return getFormat().parse(date); + if (date == null) + return null; + + // Sometimes, the date comes back without the ".SSSSSSS" part (presumably when the decimal value + // of the date is "0". Use the short format in that case. + if (date.indexOf('.') < 0) + return getShortFormat().parse(date); + else + return getFormat().parse(date); } private DateFormat getFormat() { @@ -41,4 +50,10 @@ private DateFormat getFormat() { iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); return iso8601Format; } + + private DateFormat getShortFormat() { + DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); + return iso8601Format; + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java new file mode 100644 index 0000000000000..6f514dad26d3c --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java @@ -0,0 +1,51 @@ +/** + * Copyright 2011 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.blob.implementation; + +import static org.junit.Assert.*; + +import java.util.Date; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; + +public class ISO8601DateConverterTests { + @Test + public void shortFormatWorks() throws Exception { + // Arrange + ISO8601DateConverter converter = new ISO8601DateConverter(); + String value = "2012-01-12T00:35:58Z"; + + // Act + Date result = converter.parse(value); + + // Assert + assertNotNull(result); + } + + @Test + public void longFormatWorks() throws Exception { + // Arrange + ISO8601DateConverter converter = new ISO8601DateConverter(); + String value = "2012-01-12T00:35:58.1234567Z"; + + // Act + Date result = converter.parse(value); + + // Assert + assertNotNull(result); + } +} From 06146b7c25c6ec34b1860402a0f047a33fce0697 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 17:12:31 -0800 Subject: [PATCH 15/76] Adding test for query entities continuation The test is actually not testing pagination by default because there is no way to reach the default # of entities (1,000) in a reasonable amount of time. Also adding test for query entities filtering --- .../table/TableServiceIntegrationTest.java | 86 +++++++++++++++++-- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 27420b286d082..cf45713dc8fd6 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -28,10 +28,13 @@ import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; +import com.microsoft.windowsazure.services.table.models.Filter; import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.Query; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; @@ -43,7 +46,8 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static String TEST_TABLE_1; private static String TEST_TABLE_2; private static String TEST_TABLE_3; - //private static String TEST_TABLE_4; + private static String TEST_TABLE_4; + private static String TEST_TABLE_5; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; //private static String CREATABLE_TABLE_3; @@ -67,7 +71,8 @@ public static void setup() throws Exception { TEST_TABLE_1 = testTables[0]; TEST_TABLE_2 = testTables[1]; TEST_TABLE_3 = testTables[2]; - //TEST_TABLE_4 = testTables[3]; + TEST_TABLE_4 = testTables[3]; + TEST_TABLE_5 = testTables[4]; CREATABLE_TABLE_1 = creatableTables[0]; CREATABLE_TABLE_2 = creatableTables[1]; @@ -476,13 +481,13 @@ public void getEntityWorks() throws Exception { } @Test - public void queryEntityWorks() throws Exception { - System.out.println("queryEntityWorks()"); + public void queryEntitiesWorks() throws Exception { + System.out.println("queryEntitiesWorks()"); // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); - Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntityWorks") + Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) .setProperty("test5", EdmType.DATETIME, new Date()); @@ -499,7 +504,7 @@ public void queryEntityWorks() throws Exception { assertNotNull(result.getEntities().get(0)); assertEquals("001", result.getEntities().get(0).getPartitionKey()); - assertEquals("queryEntityWorks", result.getEntities().get(0).getRowKey()); + assertEquals("queryEntitiesWorks", result.getEntities().get(0).getRowKey()); assertNotNull(result.getEntities().get(0).getTimestamp()); assertNotNull(result.getEntities().get(0).getEtag()); @@ -518,4 +523,73 @@ public void queryEntityWorks() throws Exception { assertNotNull(result.getEntities().get(0).getProperty("test5")); assertTrue(result.getEntities().get(0).getProperty("test5").getValue() instanceof Date); } + + @Test + public void queryEntitiesWithPaginationWorks() throws Exception { + System.out.println("queryEntitiesWithPaginationWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_4; + int numberOfEntries = 20; + for (int i = 0; i < numberOfEntries; i++) { + Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithPaginationWorks-" + i) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + service.insertEntity(table, entity); + } + + // Act + int entryCount = 0; + String nextPartitionKey = null; + String nextRowKey = null; + while (true) { + QueryEntitiesResult result = service.queryEntities(table, + new QueryEntitiesOptions().setNextPartitionKey(nextPartitionKey).setNextRowKey(nextRowKey)); + + entryCount += result.getEntities().size(); + + if (nextPartitionKey == null) + break; + + nextPartitionKey = result.getNextPartitionKey(); + nextRowKey = result.getNextRowKey(); + } + + // Assert + assertEquals(numberOfEntries, entryCount); + } + + @Test + public void queryEntitiesWithFilterWorks() throws Exception { + System.out.println("queryEntitiesWithFilterWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_5; + int numberOfEntries = 5; + for (int i = 0; i < numberOfEntries; i++) { + Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithFilterWorks-" + i) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + service.insertEntity(table, entity); + } + + // Act + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), + Filter.constant("queryEntitiesWithFilterWorks-3"))))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } } From 8fdbcf75d024c17cd220c37b32c668a1b1f9578f Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 11 Jan 2012 17:23:22 -0800 Subject: [PATCH 16/76] Adding a couple more unit test for query entities Also adding escape mechanism for expressing filters --- .../table/implementation/TableRestProxy.java | 4 +++ .../services/table/models/Filter.java | 4 +++ .../table/models/RawStringFilter.java | 14 +++++++++ .../table/TableServiceIntegrationTest.java | 30 +++++++++++++------ 4 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 95558d7021dd3..3f18d447b788d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -48,6 +48,7 @@ import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; +import com.microsoft.windowsazure.services.table.models.RawStringFilter; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.microsoft.windowsazure.services.table.models.UnaryFilter; @@ -188,6 +189,9 @@ else if (filter instanceof BinaryFilter) { buildFilterExpression(((BinaryFilter) filter).getRight(), sb); sb.append(")"); } + else if (filter instanceof RawStringFilter) { + sb.append(((RawStringFilter) filter).getRawString()); + } } private Builder addOptionalHeader(Builder builder, String name, Object value) { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index fcb3560758a2e..f0a54f58b09b7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -44,4 +44,8 @@ public static ConstantFilter constant(Object value) { public static LitteralFilter litteral(String value) { return new LitteralFilter().setLitteral(value); } + + public static RawStringFilter rawString(String value) { + return new RawStringFilter().setRawString(value); + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java new file mode 100644 index 0000000000000..ffc8e38aef00c --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class RawStringFilter extends Filter { + private String rawString; + + public String getRawString() { + return rawString; + } + + public RawStringFilter setRawString(String rawString) { + this.rawString = rawString; + return this; + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index cf45713dc8fd6..8e12ebac2b045 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -581,15 +581,27 @@ public void queryEntitiesWithFilterWorks() throws Exception { service.insertEntity(table, entity); } - // Act - QueryEntitiesResult result = service.queryEntities( - table, - new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), - Filter.constant("queryEntitiesWithFilterWorks-3"))))); + { + // Act + QueryEntitiesResult result = service.queryEntities(table, + new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), + Filter.constant("queryEntitiesWithFilterWorks-3"))))); - // Assert - assertNotNull(result); - assertEquals(1, result.getEntities().size()); - assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } + + { + // Act + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() + .setFilter(Filter.rawString("RowKey eq 'queryEntitiesWithFilterWorks-3'")))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } } } From 3f5f5d780d48deb6534a6d43fc5c2fd99d401ef9 Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Thu, 12 Jan 2012 16:18:06 -0800 Subject: [PATCH 17/76] Initial (not quite working yet) support for batch operations --- microsoft-azure-api/pom.xml | 5 + .../windowsazure/services/table/Exports.java | 2 + .../services/table/TableContract.java | 6 + .../implementation/MimeReaderWriter.java | 113 ++++++ .../TableExceptionProcessor.java | 28 ++ .../table/implementation/TableRestProxy.java | 104 +++++- .../table/models/BatchOperations.java | 197 ++++++++++ .../services/table/models/BatchResult.java | 5 + .../table/TableServiceIntegrationTest.java | 47 +++ .../AtomReaderWriterTests.java | 6 +- .../implementation/MimeMultipartTests.java | 336 ++++++++++++++++++ 11 files changed, 841 insertions(+), 8 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java rename microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/{ => implementation}/AtomReaderWriterTests.java (93%) create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index 5da3a62a9e6ce..36651b165c7c5 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -80,6 +80,11 @@ commons-logging 1.1.1 + + javax.mail + mail + 1.4 + diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java index 21ad0784df4b4..59322db2264df 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -18,6 +18,7 @@ import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter; import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; +import com.microsoft.windowsazure.services.table.implementation.MimeReaderWriter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter; import com.microsoft.windowsazure.services.table.implementation.TableExceptionProcessor; @@ -34,6 +35,7 @@ public void register(Builder.Registry registry) { registry.add(SharedKeyFilter.class); registry.add(XMLStreamFactory.class, DefaultXMLStreamFactory.class); registry.add(AtomReaderWriter.class); + registry.add(MimeReaderWriter.class); registry.add(EdmValueConverter.class, DefaultEdmValueConterter.class); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index c214333e8a634..63ec2c1b20865 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -16,6 +16,8 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.models.BatchOperations; +import com.microsoft.windowsazure.services.table.models.BatchResult; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetEntityResult; @@ -95,4 +97,8 @@ GetEntityResult getEntity(String table, String partitionKey, String rowKey, Tabl QueryEntitiesResult queryEntities(String table) throws ServiceException; QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException; + + BatchResult batch(BatchOperations operations) throws ServiceException; + + BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java new file mode 100644 index 0000000000000..933d3f8aac55d --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java @@ -0,0 +1,113 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.UUID; + +import javax.inject.Inject; +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.MultipartDataSource; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; + +public class MimeReaderWriter { + + @Inject + public MimeReaderWriter() { + } + + public MimeMultipart getMimeMultipart(List bodyPartContents) { + try { + return getMimeMultipartCore(bodyPartContents); + } + catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + private MimeMultipart getMimeMultipartCore(List bodyPartContents) throws MessagingException { + // Create unique part boundary strings + String batchId = String.format("batch_%s", UUID.randomUUID().toString()); + String changeSet = String.format("changeset_%s", UUID.randomUUID().toString()); + + // + // Build inner list of change sets containing the list of body part content + // + MimeMultipart changeSets = new MimeMultipart(new SetBoundaryMultipartDataSource(changeSet)); + + for (String bodyPart : bodyPartContents) { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + + mimeBodyPart.setContent(bodyPart, "application/http"); + + //Note: Both content type and encoding need to be set *after* setting content, because + // MimeBodyPart implementation replaces them when calling "setContent". + mimeBodyPart.setHeader("Content-Type", "application/http"); + mimeBodyPart.setHeader("Content-Transfer-Encoding", "binary"); + + changeSets.addBodyPart(mimeBodyPart); + } + + // + // Build outer "batch" body part + // + MimeBodyPart batchbody = new MimeBodyPart(); + batchbody.setContent(changeSets); + //Note: Both content type and encoding need to be set *after* setting content, because + // MimeBodyPart implementation replaces them when calling "setContent". + batchbody.setHeader("Content-Type", changeSets.getContentType()); + + // + // Build outer "batch" multipart + // + MimeMultipart batch = new MimeMultipart(new SetBoundaryMultipartDataSource(batchId)); + batch.addBodyPart(batchbody); + return batch; + } + + /** + * The only purpose of this class is to force the boundary of a MimeMultipart instance. + * This is done by simple passing an instance of this class to the constructor of MimeMultipart. + */ + private class SetBoundaryMultipartDataSource implements MultipartDataSource { + + private final String boundary; + + public SetBoundaryMultipartDataSource(String boundary) { + this.boundary = boundary; + } + + @Override + public String getContentType() { + return "multipart/mixed; boundary=" + boundary; + } + + @Override + public InputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return null; + } + + @Override + public int getCount() { + return 0; + } + + @Override + public BodyPart getBodyPart(int index) throws MessagingException { + return null; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 93a99570fdbaf..c8bf695b7cdda 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -23,6 +23,8 @@ import com.microsoft.windowsazure.services.core.ServiceFilter; import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.BatchOperations; +import com.microsoft.windowsazure.services.table.models.BatchResult; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.Entity; import com.microsoft.windowsazure.services.table.models.GetEntityResult; @@ -460,4 +462,30 @@ public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions opti throw processCatch(new ServiceException(e)); } } + + @Override + public BatchResult batch(BatchOperations operations) throws ServiceException { + try { + return service.batch(operations); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException { + try { + return service.batch(operations, options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 3f18d447b788d..39deca1589bf7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -14,12 +14,17 @@ */ package com.microsoft.windowsazure.services.table.implementation; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.inject.Inject; import javax.inject.Named; +import javax.mail.internet.MimeMultipart; import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; @@ -32,6 +37,10 @@ import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; import com.microsoft.windowsazure.services.table.TableConfiguration; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.models.BatchOperations; +import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.Operation; +import com.microsoft.windowsazure.services.table.models.BatchResult; import com.microsoft.windowsazure.services.table.models.BinaryFilter; import com.microsoft.windowsazure.services.table.models.ConstantFilter; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; @@ -67,11 +76,12 @@ public class TableRestProxy implements TableContract { private final ServiceFilter[] filters; private final SharedKeyFilter filter; private final AtomReaderWriter atomReaderWriter; + private final MimeReaderWriter mimeReaderWriter; @Inject public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.URI) String url, SharedKeyFilter filter, DateFactory dateFactory, ISO8601DateConverter iso8601DateConverter, - AtomReaderWriter atomReaderWriter) { + AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter) { this.channel = channel; this.url = url; @@ -81,12 +91,13 @@ public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration this.filters = new ServiceFilter[0]; this.dateFactory = dateFactory; this.atomReaderWriter = atomReaderWriter; + this.mimeReaderWriter = mimeReaderWriter; channel.addFilter(filter); } public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String url, SharedKeyFilter filter, - DateFactory dateFactory, AtomReaderWriter atomReaderWriter, RFC1123DateConverter dateMapper, - ISO8601DateConverter iso8601DateConverter) { + DateFactory dateFactory, AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter, + RFC1123DateConverter dateMapper, ISO8601DateConverter iso8601DateConverter) { this.channel = channel; this.filters = filters; @@ -94,6 +105,7 @@ public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, this.filter = filter; this.dateFactory = dateFactory; this.atomReaderWriter = atomReaderWriter; + this.mimeReaderWriter = mimeReaderWriter; this.dateMapper = dateMapper; this.iso8601DateConverter = iso8601DateConverter; } @@ -103,7 +115,7 @@ public TableContract withFilter(ServiceFilter filter) { ServiceFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1); newFilters[filters.length] = filter; return new TableRestProxy(this.channel, newFilters, this.url, this.filter, this.dateFactory, - this.atomReaderWriter, this.dateMapper, this.iso8601DateConverter); + this.atomReaderWriter, this.mimeReaderWriter, this.dateMapper, this.iso8601DateConverter); } private void ThrowIfError(ClientResponse r) { @@ -508,4 +520,88 @@ public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions opti return result; } + + @Override + public BatchResult batch(BatchOperations operations) throws ServiceException { + return batch(operations, new TableServiceOptions()); + } + + @Override + public BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException { + WebResource webResource = getResource(options).path("$batch"); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + MimeMultipart entity = createMimeMultipart(operations); + builder = builder.type(entity.getContentType()); + + ClientResponse response = builder.post(ClientResponse.class, entity); + ThrowIfError(response); + + return null; + } + + private MimeMultipart createMimeMultipart(BatchOperations operations) { + try { + List bodyPartContents = new ArrayList(); + int contentId = 1; + for (Operation operation : operations.getOperations()) { + + String bodyPartContent = null; + // INSERT + if (operation instanceof InsertOperation) { + InsertOperation op = (InsertOperation) operation; + + //TODO: Review code to make sure encoding is correct + InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); + byte[] bytes = inputStreamToByteArray(stream); + String content = new String(bytes, "UTF-8"); + + StringBuilder sb = new StringBuilder(); + sb.append(String.format("POST %s HTTP/1.1\r\n", channel.resource(url).path(op.getTable()).getURI())); + sb.append(String.format("Content-ID: %d\r\n", contentId++)); + sb.append("Content-Type: application/atom+xml;type=entry\r\n"); + sb.append(String.format("Content-Length: %d\r\n", content.length())); + sb.append("\r\n"); + sb.append(content); + + bodyPartContent = sb.toString(); + } + + if (bodyPartContent != null) { + bodyPartContents.add(bodyPartContent); + } + } + + return mimeReaderWriter.getMimeMultipart(bodyPartContents); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private byte[] inputStreamToByteArray(InputStream inputStream) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + byte[] buffer = new byte[1024]; + try { + while (true) { + int n = inputStream.read(buffer); + if (n == -1) + break; + outputStream.write(buffer, 0, n); + } + } + finally { + inputStream.close(); + } + return outputStream.toByteArray(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java new file mode 100644 index 0000000000000..43c93c22b0f85 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -0,0 +1,197 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.ArrayList; +import java.util.List; + +public class BatchOperations { + private List operations = new ArrayList(); + + public List getOperations() { + return operations; + } + + public void setOperations(List operations) { + this.operations = operations; + } + + public BatchOperations addInsertEntity(String table, Entity entity) { + this.operations.add(new InsertOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addUpdateEntity(String table, Entity entity) { + this.operations.add(new UpdateOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addMergeEntity(String table, Entity entity) { + this.operations.add(new MergeOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addInsertOrReplaceEntity(String table, Entity entity) { + this.operations.add(new InsertOrReplaceOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { + this.operations.add(new InsertOrMergeOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey) { + this.operations.add(new DeleteOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); + return this; + } + + public abstract class Operation { + } + + public class InsertOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public class UpdateOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public UpdateOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public UpdateOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public class MergeOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public MergeOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public MergeOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public class InsertOrReplaceOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertOrReplaceOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertOrReplaceOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public class InsertOrMergeOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertOrMergeOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertOrMergeOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public class DeleteOperation extends Operation { + private String table; + private String partitionKey; + private String rowKey; + + public String getTable() { + return table; + } + + public DeleteOperation setTable(String table) { + this.table = table; + return this; + } + + public String getPartitionKey() { + return partitionKey; + } + + public DeleteOperation setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + return this; + } + + public String getRowKey() { + return rowKey; + } + + public DeleteOperation setRowKey(String rowKey) { + this.rowKey = rowKey; + return this; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java new file mode 100644 index 0000000000000..462dc4d10ef63 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -0,0 +1,5 @@ +package com.microsoft.windowsazure.services.table.models; + +public class BatchResult { + +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 8e12ebac2b045..c489c675735ad 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -25,6 +25,11 @@ import org.junit.Test; import com.microsoft.windowsazure.services.core.Configuration; +import com.microsoft.windowsazure.services.core.ExponentialRetryPolicy; +import com.microsoft.windowsazure.services.core.RetryPolicyFilter; +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.models.BatchOperations; +import com.microsoft.windowsazure.services.table.models.BatchResult; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; @@ -48,6 +53,7 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static String TEST_TABLE_3; private static String TEST_TABLE_4; private static String TEST_TABLE_5; + private static String TEST_TABLE_6; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; //private static String CREATABLE_TABLE_3; @@ -56,6 +62,9 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { @BeforeClass public static void setup() throws Exception { + //System.setProperty("http.proxyHost", "127.0.0.1"); + //System.setProperty("http.proxyPort", "8888"); + // Setup container names array (list of container names used by // integration tests) testTables = new String[10]; @@ -73,6 +82,7 @@ public static void setup() throws Exception { TEST_TABLE_3 = testTables[2]; TEST_TABLE_4 = testTables[3]; TEST_TABLE_5 = testTables[4]; + TEST_TABLE_6 = testTables[5]; CREATABLE_TABLE_1 = creatableTables[0]; CREATABLE_TABLE_2 = creatableTables[1]; @@ -82,6 +92,8 @@ public static void setup() throws Exception { Configuration config = createConfiguration(); TableContract service = TableService.create(config); + deleteAllTables(service, testTables); + deleteAllTables(service, creatableTables); createTables(service, testTablesPrefix, testTables); } @@ -95,6 +107,9 @@ public static void cleanup() throws Exception { } private static void createTables(TableContract service, String prefix, String[] list) throws Exception { + // Retry creating every table as long as we get "409 - Table being deleted" error + service = service.withFilter(new RetryPolicyFilter(new ExponentialRetryPolicy(new int[] { 409 }))); + Set containers = listTables(service, prefix); for (String item : list) { if (!containers.contains(item)) { @@ -112,6 +127,17 @@ private static void deleteTables(TableContract service, String prefix, String[] } } + private static void deleteAllTables(TableContract service, String[] list) throws Exception { + for (String item : list) { + try { + service.deleteTable(item); + } + catch (ServiceException e) { + // Ignore + } + } + } + private static Set listTables(TableContract service, String prefix) throws Exception { HashSet result = new HashSet(); QueryTablesResult list = service.listTables(new ListTablesOptions().setPrefix(prefix)); @@ -604,4 +630,25 @@ public void queryEntitiesWithFilterWorks() throws Exception { assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); } } + + @Test + public void batchWorks() throws Exception { + System.out.println("batchWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + // Act + BatchResult result = service.batch(new BatchOperations().addInsertEntity(table, entity)); + + // Assert + assertNotNull(result); + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java similarity index 93% rename from microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java rename to microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java index 9aebd223e75bd..aa14a71f8597c 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/AtomReaderWriterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.microsoft.windowsazure.services.table; +package com.microsoft.windowsazure.services.table.implementation; import static org.junit.Assert.*; @@ -24,9 +24,7 @@ import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.core.utils.DefaultDateFactory; -import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; -import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter; -import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; +import com.microsoft.windowsazure.services.table.IntegrationTestBase; import com.microsoft.windowsazure.services.table.models.TableEntry; public class AtomReaderWriterTests extends IntegrationTestBase { diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java new file mode 100644 index 0000000000000..c01fdeaebc3cf --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java @@ -0,0 +1,336 @@ +/** + * Copyright 2011 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.implementation; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; + +import javax.activation.DataSource; +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.MultipartDataSource; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMultipart; +import javax.mail.util.ByteArrayDataSource; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.table.IntegrationTestBase; + +public class MimeMultipartTests extends IntegrationTestBase { + @Test + public void parseMimeWorks() throws Exception { + //@formatter:off + String s = "--batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb \r\n" + + "Content-Type: multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "\r\n" + + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "Content-Type: application/http\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "HTTP/1.1 201 Created\r\n" + + "Content-ID: 1\r\n" + + "Content-Type: application/atom+xml;charset=utf-8\r\n" + + "Cache-Control: no-cache\r\n" + + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n" + + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n" + + "DataServiceVersion: 1.0;\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n" + + " \r\n" + + " 2009-04-30T20:44:09Z\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " Channel_19\r\n" + + " 1\r\n" + + " 2009-04-30T20:44:09.5789464Z\r\n" + + " .Net...\r\n" + + " 9\r\n" + + " \r\n" + + " \r\n" + + "\r\n" + + "\r\n" + + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "Content-Type: application/http\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "HTTP/1.1 201 Created\r\n" + + "Content-ID: 2\r\n" + + "Content-Type: application/atom+xml;charset=utf-8\r\n" + + "Cache-Control: no-cache\r\n" + + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n" + + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n" + + "DataServiceVersion: 1.0;\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n" + + " \r\n" + + " 2009-04-30T20:44:09Z\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " Channel_19\r\n" + + " 2\r\n" + + " 2009-04-30T20:44:09.5789464Z\r\n" + + " Azure...\r\n" + + " 9\r\n" + + " \r\n" + + " \r\n" + + "\r\n" + + "\r\n" + + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "Content-Type: application/http\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "HTTP/1.1 204 No Content\r\n" + + "Content-ID: 3\r\n" + + "Cache-Control: no-cache\r\n" + + "ETag: W/\"datetime'2009-04-30T20%3A44%3A10.0019041Z'\"\r\n" + + "DataServiceVersion: 1.0;\r\n" + + "\r\n" + + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "Content-Type: application/http\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "HTTP/1.1 204 No Content\r\n" + + "Content-ID: 4\r\n" + + "Cache-Control: no-cache\r\n" + + "DataServiceVersion: 1.0;\r\n" + + "\r\n" + + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977--\r\n" + + "--batchresponse_4c637ba4-b2f8-40f8-8856-c2d10d163a83--\r\n"; + //@formatter:on + + DataSource ds = new ByteArrayDataSource(s, + "multipart/mixed; boundary=batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb"); + MimeMultipart m = new MimeMultipart(ds); + + assertEquals(1, m.getCount()); + assertTrue(m.getBodyPart(0) instanceof MimeBodyPart); + + MimeBodyPart part = (MimeBodyPart) m.getBodyPart(0); + String contentType = part.getHeader("Content-Type", ":"); + assertEquals("multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977", contentType); + + DataSource ds2 = new ByteArrayDataSource(part.getInputStream(), contentType); + MimeMultipart m2 = new MimeMultipart(ds2); + + assertEquals(4, m2.getCount()); + } + + @Test + public void buildMimeWorks() throws Exception { + //@formatter:off + String changeset1 = "POST http://myaccount.tables.core.windows.net/Blogs HTTP/1.1\r\n" + + "Content-ID: 1\r\n" + + "Content-Type: application/atom+xml;type=entry\r\n" + + "Content-Length: ###\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + " \r\n" + + " <updated>2009-04-30T20:45:13.7155321Z</updated>\r\n" + + " <author>\r\n" + + " <name />\r\n" + + " </author>\r\n" + + " <id />\r\n" + + " <content type=\"application/xml\">\r\n" + + " <m:properties>\r\n" + + " <d:PartitionKey>Channel_19</d:PartitionKey>\r\n" + + " <d:RowKey>1</d:RowKey>\r\n" + + " <d:Timestamp m:type=\"Edm.DateTime\">0001-01-01T00:00:00</d:Timestamp>\r\n" + + " <d:Rating m:type=\"Edm.Int32\">9</d:Rating>\r\n" + + " <d:Text>.NET...</d:Title>\r\n" + + " </m:properties>\r\n" + + " </content>\r\n" + + "</entry>"; + //@formatter:on + + // + // Build inner list of change sets + // + + MimeMultipart changeSets = new MimeMultipart(new SetBoundaryMultipartDataSource( + "changeset_8a28b620-b4bb-458c-a177-0959fb14c977")); + + MimeBodyPart cs1 = new MimeBodyPart(); + cs1.setContent(changeset1, "application/http"); + changeSets.addBodyPart(cs1); + + MimeBodyPart cs2 = new MimeBodyPart(); + cs2.setContent(changeset1, "application/http"); + changeSets.addBodyPart(cs2); + + // + // Build outer "batch" body part + // + MimeBodyPart batchbody = new MimeBodyPart(); + batchbody.setContent(changeSets); + + // + // Build outer "batch" multipart + // + MimeMultipart batch = new MimeMultipart(new SetBoundaryMultipartDataSource( + "batch_a1e9d677-b28b-435e-a89e-87e6a768a431")); + batch.addBodyPart(batchbody); + + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + batch.writeTo(stream); + + String result = stream.toString("UTF-8"); + //@formatter:off + String expectedResult = + "--batch_a1e9d677-b28b-435e-a89e-87e6a768a431\r\n" + + "\r\n" + + "--changeset_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "\r\n" + + "POST http://myaccount.tables.core.windows.net/Blogs HTTP/1.1\r\n" + + "Content-ID: 1\r\n" + + "Content-Type: application/atom+xml;type=entry\r\n" + + "Content-Length: ###\r\n" + + "\r\n" + + "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n" + + "<entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\r\n" + + " <title />\r\n" + + " <updated>2009-04-30T20:45:13.7155321Z</updated>\r\n" + + " <author>\r\n" + + " <name />\r\n" + + " </author>\r\n" + + " <id />\r\n" + + " <content type=\"application/xml\">\r\n" + + " <m:properties>\r\n" + + " <d:PartitionKey>Channel_19</d:PartitionKey>\r\n" + + " <d:RowKey>1</d:RowKey>\r\n" + + " <d:Timestamp m:type=\"Edm.DateTime\">0001-01-01T00:00:00</d:Timestamp>\r\n" + + " <d:Rating m:type=\"Edm.Int32\">9</d:Rating>\r\n" + + " <d:Text>.NET...</d:Title>\r\n" + + " </m:properties>\r\n" + + " </content>\r\n" + + "</entry>\r\n" + + "--changeset_8a28b620-b4bb-458c-a177-0959fb14c977\r\n" + + "\r\n" + + "POST http://myaccount.tables.core.windows.net/Blogs HTTP/1.1\r\n" + + "Content-ID: 1\r\n" + + "Content-Type: application/atom+xml;type=entry\r\n" + + "Content-Length: ###\r\n" + + "\r\n" + + "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\r\n" + + "<entry xmlns:d=\"http://schemas.microsoft.com/ado/2007/08/dataservices\" xmlns:m=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\" xmlns=\"http://www.w3.org/2005/Atom\">\r\n" + + " <title />\r\n" + + " <updated>2009-04-30T20:45:13.7155321Z</updated>\r\n" + + " <author>\r\n" + + " <name />\r\n" + + " </author>\r\n" + + " <id />\r\n" + + " <content type=\"application/xml\">\r\n" + + " <m:properties>\r\n" + + " <d:PartitionKey>Channel_19</d:PartitionKey>\r\n" + + " <d:RowKey>1</d:RowKey>\r\n" + + " <d:Timestamp m:type=\"Edm.DateTime\">0001-01-01T00:00:00</d:Timestamp>\r\n" + + " <d:Rating m:type=\"Edm.Int32\">9</d:Rating>\r\n" + + " <d:Text>.NET...</d:Title>\r\n" + + " </m:properties>\r\n" + + " </content>\r\n" + + "</entry>\r\n" + + "--changeset_8a28b620-b4bb-458c-a177-0959fb14c977--\r\n" + + "\r\n" + + "--batch_a1e9d677-b28b-435e-a89e-87e6a768a431--\r\n"; + //@formatter:on + StringReader reader1 = new StringReader(result); + StringReader reader2 = new StringReader(expectedResult); + + for (int i = 0;; i++) { + int ch1 = reader1.read(); + int ch2 = reader2.read(); + if (ch1 == -1) { + assertEquals(-1, ch2); + break; + } + if (ch2 == -1) { + assertEquals(-1, ch1); + break; + } + + if (ch1 != ch2) { + int min1 = Math.max(0, i - 20); + int max1 = Math.min(result.length(), i + 20); + + int min2 = Math.max(0, i - 20); + int max2 = Math.min(expectedResult.length(), i + 20); + + String closeBy1 = result.substring(min1, max1); + String closeBy2 = expectedResult.substring(min2, max2); + + assertEquals("Message content are no equal starting at position " + i, closeBy2, closeBy1); + } + } + } + + private class SetBoundaryMultipartDataSource implements MultipartDataSource { + + private final String boundary; + + public SetBoundaryMultipartDataSource(String boundary) { + this.boundary = boundary; + } + + @Override + public String getContentType() { + return "multipart/mixed; boundary=" + boundary; + } + + @Override + public InputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getName() { + return null; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return null; + } + + @Override + public int getCount() { + return 0; + } + + @Override + public BodyPart getBodyPart(int index) throws MessagingException { + return null; + } + } +} From 802e9b0f69ac0b7e35a0daf143dd2b934f0e4d34 Mon Sep 17 00:00:00 2001 From: Renaud Paquay <renaud.paquay@microsoft.com> Date: Fri, 13 Jan 2012 16:44:36 -0800 Subject: [PATCH 18/76] Add full support for "batch" operation on tables --- .../core/utils/pipeline/PipelineHelpers.java | 18 +- .../windowsazure/services/table/Exports.java | 2 + .../implementation/HttpReaderWriter.java | 168 ++++++++++++++++++ .../implementation/InputStreamDataSource.java | 38 ++++ .../implementation/MimeReaderWriter.java | 50 +++++- .../table/implementation/TableRestProxy.java | 164 +++++++++++++---- .../table/models/BatchOperations.java | 50 +++--- .../services/table/models/BatchResult.java | 60 +++++++ .../table/TableServiceIntegrationTest.java | 3 + 9 files changed, 473 insertions(+), 80 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java index 9cebe855ee2a6..a36de8dc02476 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.core.utils.pipeline; @@ -28,7 +28,7 @@ public class PipelineHelpers { public static void ThrowIfError(ClientResponse r) { - if (r.getStatus() >= 300) { + if (r.getStatus() >= 400) { throw new UniformInterfaceException(r); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java index 59322db2264df..ee52dd47bfb29 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -18,6 +18,7 @@ import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter; import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter; import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory; +import com.microsoft.windowsazure.services.table.implementation.HttpReaderWriter; import com.microsoft.windowsazure.services.table.implementation.MimeReaderWriter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter; import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter; @@ -36,6 +37,7 @@ public void register(Builder.Registry registry) { registry.add(XMLStreamFactory.class, DefaultXMLStreamFactory.class); registry.add(AtomReaderWriter.class); registry.add(MimeReaderWriter.class); + registry.add(HttpReaderWriter.class); registry.add(EdmValueConverter.class, DefaultEdmValueConterter.class); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java new file mode 100644 index 0000000000000..458acf6e0cd96 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java @@ -0,0 +1,168 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.Enumeration; + +import javax.activation.DataSource; +import javax.inject.Inject; +import javax.mail.Header; +import javax.mail.MessagingException; +import javax.mail.internet.InternetHeaders; + +import com.sun.mail.util.LineInputStream; + +public class HttpReaderWriter { + + @Inject + public HttpReaderWriter() { + } + + public StatusLine parseStatusLine(DataSource ds) { + try { + LineInputStream stream = new LineInputStream(ds.getInputStream()); + String line = stream.readLine(); + StringReader lineReader = new StringReader(line); + + expect(lineReader, "HTTP/1.1"); + expect(lineReader, " "); + String statusString = extractInput(lineReader, ' '); + String reason = extractInput(lineReader, -1); + + return new StatusLine().setStatus(Integer.parseInt(statusString)).setReason(reason); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public InternetHeaders parseHeaders(DataSource ds) { + try { + return new InternetHeaders(ds.getInputStream()); + } + catch (MessagingException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public InputStream parseEntity(DataSource ds) { + try { + return ds.getInputStream(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void appendMethod(OutputStream stream, String verb, URI uri) { + try { + String method = String.format("%s %s %s\r\n", verb, uri, "HTTP/1.1"); + stream.write(method.getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void appendHeaders(OutputStream stream, InternetHeaders headers) { + try { + // Headers + Enumeration<Header> e = headers.getAllHeaders(); + while (e.hasMoreElements()) { + Header header = e.nextElement(); + + String headerLine = String.format("%s: %s\r\n", header.getName(), header.getValue()); + stream.write(headerLine.getBytes("UTF-8")); + } + + // Empty line + stream.write("\r\n".getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void appendEntity(OutputStream stream, InputStream entity) { + try { + byte[] buffer = new byte[1024]; + while (true) { + int n = entity.read(buffer); + if (n == -1) + break; + stream.write(buffer, 0, n); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void expect(Reader reader, String string) { + try { + for (int i = 0; i < string.length(); i++) { + int ch = reader.read(); + if (ch < 0) + throw new RuntimeException(String.format("Expected '%s', found '%s' instead", string, + string.substring(0, i) + ch)); + } + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private String extractInput(Reader reader, int delimiter) { + try { + StringBuilder sb = new StringBuilder(); + while (true) { + int ch = reader.read(); + if (ch == -1 || ch == delimiter) + break; + + sb.append((char) ch); + } + return sb.toString(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + public class StatusLine { + private int status; + private String reason; + + public int getStatus() { + return status; + } + + public StatusLine setStatus(int status) { + this.status = status; + return this; + } + + public String getReason() { + return reason; + } + + public StatusLine setReason(String reason) { + this.reason = reason; + return this; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java new file mode 100644 index 0000000000000..22c099a52fee3 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java @@ -0,0 +1,38 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.activation.DataSource; + +public class InputStreamDataSource implements DataSource { + private final InputStream stream; + private final String contentType; + + public InputStreamDataSource(InputStream stream, String contentType) { + this.stream = stream; + this.contentType = contentType; + + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public InputStream getInputStream() throws IOException { + return stream; + } + + @Override + public String getName() { + return null; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return null; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java index 933d3f8aac55d..9d2282ab08e68 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java @@ -3,15 +3,19 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.List; import java.util.UUID; +import javax.activation.DataHandler; +import javax.activation.DataSource; import javax.inject.Inject; import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.MultipartDataSource; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimePartDataSource; public class MimeReaderWriter { @@ -19,16 +23,20 @@ public class MimeReaderWriter { public MimeReaderWriter() { } - public MimeMultipart getMimeMultipart(List<String> bodyPartContents) { + public MimeMultipart getMimeMultipart(List<DataSource> bodyPartContents) { try { return getMimeMultipartCore(bodyPartContents); } catch (MessagingException e) { throw new RuntimeException(e); } + catch (IOException e) { + throw new RuntimeException(e); + } } - private MimeMultipart getMimeMultipartCore(List<String> bodyPartContents) throws MessagingException { + private MimeMultipart getMimeMultipartCore(List<DataSource> bodyPartContents) throws MessagingException, + IOException { // Create unique part boundary strings String batchId = String.format("batch_%s", UUID.randomUUID().toString()); String changeSet = String.format("changeset_%s", UUID.randomUUID().toString()); @@ -38,14 +46,11 @@ private MimeMultipart getMimeMultipartCore(List<String> bodyPartContents) throws // MimeMultipart changeSets = new MimeMultipart(new SetBoundaryMultipartDataSource(changeSet)); - for (String bodyPart : bodyPartContents) { + for (DataSource bodyPart : bodyPartContents) { MimeBodyPart mimeBodyPart = new MimeBodyPart(); - mimeBodyPart.setContent(bodyPart, "application/http"); - - //Note: Both content type and encoding need to be set *after* setting content, because - // MimeBodyPart implementation replaces them when calling "setContent". - mimeBodyPart.setHeader("Content-Type", "application/http"); + mimeBodyPart.setDataHandler(new DataHandler(bodyPart)); + mimeBodyPart.setHeader("Content-Type", bodyPart.getContentType()); mimeBodyPart.setHeader("Content-Transfer-Encoding", "binary"); changeSets.addBodyPart(mimeBodyPart); @@ -110,4 +115,33 @@ public BodyPart getBodyPart(int index) throws MessagingException { return null; } } + + public List<DataSource> parseParts(final InputStream entityInputStream, final String contentType) { + try { + return parsePartsCore(entityInputStream, contentType); + } + catch (IOException e) { + throw new RuntimeException(e); + } + catch (MessagingException e) { + throw new RuntimeException(e); + } + } + + private List<DataSource> parsePartsCore(InputStream entityInputStream, String contentType) + throws MessagingException, IOException { + DataSource ds = new InputStreamDataSource(entityInputStream, contentType); + MimeMultipart batch = new MimeMultipart(ds); + MimeBodyPart batchBody = (MimeBodyPart) batch.getBodyPart(0); + + MimeMultipart changeSets = new MimeMultipart(new MimePartDataSource(batchBody)); + + List<DataSource> result = new ArrayList<DataSource>(); + for (int i = 0; i < changeSets.getCount(); i++) { + BodyPart part = changeSets.getBodyPart(i); + + result.add(new InputStreamDataSource(part.getInputStream(), part.getContentType())); + } + return result; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 39deca1589bf7..944bb8214424a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -14,16 +14,20 @@ */ package com.microsoft.windowsazure.services.table.implementation; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.List; +import javax.activation.DataSource; import javax.inject.Inject; import javax.inject.Named; +import javax.mail.Header; +import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeMultipart; import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; @@ -32,15 +36,27 @@ import com.microsoft.windowsazure.services.core.ServiceFilter; import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder; import com.microsoft.windowsazure.services.core.utils.DateFactory; +import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory; import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter; import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient; import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers; import com.microsoft.windowsazure.services.table.TableConfiguration; import com.microsoft.windowsazure.services.table.TableContract; +import com.microsoft.windowsazure.services.table.implementation.HttpReaderWriter.StatusLine; import com.microsoft.windowsazure.services.table.models.BatchOperations; -import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.DeleteEntityOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertEntityOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOrMergeEntityOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOrReplaceEntityOperation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.MergeEntityOperation; import com.microsoft.windowsazure.services.table.models.BatchOperations.Operation; +import com.microsoft.windowsazure.services.table.models.BatchOperations.UpdateEntityOperation; import com.microsoft.windowsazure.services.table.models.BatchResult; +import com.microsoft.windowsazure.services.table.models.BatchResult.DeleteEntity; +import com.microsoft.windowsazure.services.table.models.BatchResult.Entry; +import com.microsoft.windowsazure.services.table.models.BatchResult.Error; +import com.microsoft.windowsazure.services.table.models.BatchResult.InsertEntity; +import com.microsoft.windowsazure.services.table.models.BatchResult.UpdateEntity; import com.microsoft.windowsazure.services.table.models.BinaryFilter; import com.microsoft.windowsazure.services.table.models.ConstantFilter; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; @@ -63,8 +79,10 @@ import com.microsoft.windowsazure.services.table.models.UnaryFilter; import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.UniformInterfaceException; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.core.header.InBoundHeaders; public class TableRestProxy implements TableContract { private static final String API_VERSION = "2011-08-18"; @@ -77,11 +95,12 @@ public class TableRestProxy implements TableContract { private final SharedKeyFilter filter; private final AtomReaderWriter atomReaderWriter; private final MimeReaderWriter mimeReaderWriter; + private final HttpReaderWriter httpReaderWriter; @Inject public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.URI) String url, SharedKeyFilter filter, DateFactory dateFactory, ISO8601DateConverter iso8601DateConverter, - AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter) { + AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter, HttpReaderWriter httpReaderWriter) { this.channel = channel; this.url = url; @@ -92,12 +111,14 @@ public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration this.dateFactory = dateFactory; this.atomReaderWriter = atomReaderWriter; this.mimeReaderWriter = mimeReaderWriter; + this.httpReaderWriter = httpReaderWriter; channel.addFilter(filter); } public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String url, SharedKeyFilter filter, DateFactory dateFactory, AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter, - RFC1123DateConverter dateMapper, ISO8601DateConverter iso8601DateConverter) { + HttpReaderWriter httpReaderWriter, RFC1123DateConverter dateMapper, + ISO8601DateConverter iso8601DateConverter) { this.channel = channel; this.filters = filters; @@ -106,6 +127,7 @@ public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, this.dateFactory = dateFactory; this.atomReaderWriter = atomReaderWriter; this.mimeReaderWriter = mimeReaderWriter; + this.httpReaderWriter = httpReaderWriter; this.dateMapper = dateMapper; this.iso8601DateConverter = iso8601DateConverter; } @@ -115,7 +137,8 @@ public TableContract withFilter(ServiceFilter filter) { ServiceFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1); newFilters[filters.length] = filter; return new TableRestProxy(this.channel, newFilters, this.url, this.filter, this.dateFactory, - this.atomReaderWriter, this.mimeReaderWriter, this.dateMapper, this.iso8601DateConverter); + this.atomReaderWriter, this.mimeReaderWriter, this.httpReaderWriter, this.dateMapper, + this.iso8601DateConverter); } private void ThrowIfError(ClientResponse r) { @@ -539,46 +562,111 @@ public BatchResult batch(BatchOperations operations, TableServiceOptions options ClientResponse response = builder.post(ClientResponse.class, entity); ThrowIfError(response); - return null; + BatchResult result = new BatchResult(); + result.setEntries(parseMimeMultipart(response, operations)); + + return result; } - private MimeMultipart createMimeMultipart(BatchOperations operations) { - try { - List<String> bodyPartContents = new ArrayList<String>(); - int contentId = 1; - for (Operation operation : operations.getOperations()) { - - String bodyPartContent = null; - // INSERT - if (operation instanceof InsertOperation) { - InsertOperation op = (InsertOperation) operation; - - //TODO: Review code to make sure encoding is correct - InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); - byte[] bytes = inputStreamToByteArray(stream); - String content = new String(bytes, "UTF-8"); - - StringBuilder sb = new StringBuilder(); - sb.append(String.format("POST %s HTTP/1.1\r\n", channel.resource(url).path(op.getTable()).getURI())); - sb.append(String.format("Content-ID: %d\r\n", contentId++)); - sb.append("Content-Type: application/atom+xml;type=entry\r\n"); - sb.append(String.format("Content-Length: %d\r\n", content.length())); - sb.append("\r\n"); - sb.append(content); - - bodyPartContent = sb.toString(); - } + private List<Entry> parseMimeMultipart(ClientResponse response, BatchOperations operations) { + List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() + .getFirst("Content-Type")); + + if (parts.size() != operations.getOperations().size()) { + throw new UniformInterfaceException(String.format( + "Batch response from server does not contain the correct amount " + + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations() + .size()), response); + } - if (bodyPartContent != null) { - bodyPartContents.add(bodyPartContent); + List<Entry> result = new ArrayList<Entry>(); + for (int i = 0; i < parts.size(); i++) { + DataSource ds = parts.get(i); + Operation operation = operations.getOperations().get(i); + + StatusLine status = httpReaderWriter.parseStatusLine(ds); + InternetHeaders headers = httpReaderWriter.parseHeaders(ds); + InputStream content = httpReaderWriter.parseEntity(ds); + + if (status.getStatus() >= 400) { + // Create dummy client response with status, headers and content + InBoundHeaders inBoundHeaders = new InBoundHeaders(); + + Enumeration<Header> e = headers.getAllHeaders(); + while (e.hasMoreElements()) { + Header header = e.nextElement(); + inBoundHeaders.putSingle(header.getName(), header.getValue()); } - } - return mimeReaderWriter.getMimeMultipart(bodyPartContents); + ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, null); + + // Wrap into a ServiceException + UniformInterfaceException exception = new UniformInterfaceException(dummyResponse); + ServiceException serviceException = new ServiceException(exception); + serviceException = ServiceExceptionFactory.process("table", serviceException); + Error error = new Error().setError(serviceException); + + result.add(error); + } + else if (operation instanceof InsertEntityOperation) { + InsertEntity opResult = new InsertEntity().setEntity(atomReaderWriter.parseEntityEntry(content)); + result.add(opResult); + } + else if ((operation instanceof UpdateEntityOperation) || (operation instanceof MergeEntityOperation) + || (operation instanceof InsertOrReplaceEntityOperation) + || (operation instanceof InsertOrMergeEntityOperation)) { + UpdateEntity opResult = new UpdateEntity().setEtag(headers.getHeader("ETag", null)); + result.add(opResult); + } + else if (operation instanceof DeleteEntityOperation) { + DeleteEntity opResult = new DeleteEntity(); + result.add(opResult); + } } - catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); + + return result; + } + + private MimeMultipart createMimeMultipart(BatchOperations operations) { + List<DataSource> bodyPartContents = new ArrayList<DataSource>(); + int contentId = 1; + for (Operation operation : operations.getOperations()) { + + DataSource bodyPartContent = null; + // INSERT + if (operation instanceof InsertEntityOperation) { + InsertEntityOperation op = (InsertEntityOperation) operation; + + // + // Stream content into byte[] so that we have the length + // + InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); + byte[] bytes = inputStreamToByteArray(stream); + + // + // Create body of MIME part as the HTTP request + // + InternetHeaders headers = new InternetHeaders(); + headers.addHeader("Content-ID", Integer.toString(contentId++)); + headers.addHeader("Content-Type", "application/atom+xml;type=entry"); + headers.addHeader("Content-Length", Integer.toString(bytes.length)); + + //TODO: Review code to make sure encoding is correct + ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); + httpReaderWriter.appendMethod(httpRequest, "POST", channel.resource(url).path(op.getTable()).getURI()); + httpReaderWriter.appendHeaders(httpRequest, headers); + httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes)); + + bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), + "application/http"); + } + + if (bodyPartContent != null) { + bodyPartContents.add(bodyPartContent); + } } + + return mimeReaderWriter.getMimeMultipart(bodyPartContents); } private byte[] inputStreamToByteArray(InputStream inputStream) { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index 43c93c22b0f85..12eb2a98314e3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -15,39 +15,39 @@ public void setOperations(List<Operation> operations) { } public BatchOperations addInsertEntity(String table, Entity entity) { - this.operations.add(new InsertOperation().setTable(table).setEntity(entity)); + this.operations.add(new InsertEntityOperation().setTable(table).setEntity(entity)); return this; } public BatchOperations addUpdateEntity(String table, Entity entity) { - this.operations.add(new UpdateOperation().setTable(table).setEntity(entity)); + this.operations.add(new UpdateEntityOperation().setTable(table).setEntity(entity)); return this; } public BatchOperations addMergeEntity(String table, Entity entity) { - this.operations.add(new MergeOperation().setTable(table).setEntity(entity)); + this.operations.add(new MergeEntityOperation().setTable(table).setEntity(entity)); return this; } public BatchOperations addInsertOrReplaceEntity(String table, Entity entity) { - this.operations.add(new InsertOrReplaceOperation().setTable(table).setEntity(entity)); + this.operations.add(new InsertOrReplaceEntityOperation().setTable(table).setEntity(entity)); return this; } public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { - this.operations.add(new InsertOrMergeOperation().setTable(table).setEntity(entity)); + this.operations.add(new InsertOrMergeEntityOperation().setTable(table).setEntity(entity)); return this; } public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey) { - this.operations.add(new DeleteOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); + this.operations.add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); return this; } public abstract class Operation { } - public class InsertOperation extends Operation { + public class InsertEntityOperation extends Operation { private String table; private Entity entity; @@ -55,7 +55,7 @@ public String getTable() { return table; } - public InsertOperation setTable(String table) { + public InsertEntityOperation setTable(String table) { this.table = table; return this; } @@ -64,13 +64,13 @@ public Entity getEntity() { return entity; } - public InsertOperation setEntity(Entity entity) { + public InsertEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } - public class UpdateOperation extends Operation { + public class UpdateEntityOperation extends Operation { private String table; private Entity entity; @@ -78,7 +78,7 @@ public String getTable() { return table; } - public UpdateOperation setTable(String table) { + public UpdateEntityOperation setTable(String table) { this.table = table; return this; } @@ -87,13 +87,13 @@ public Entity getEntity() { return entity; } - public UpdateOperation setEntity(Entity entity) { + public UpdateEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } - public class MergeOperation extends Operation { + public class MergeEntityOperation extends Operation { private String table; private Entity entity; @@ -101,7 +101,7 @@ public String getTable() { return table; } - public MergeOperation setTable(String table) { + public MergeEntityOperation setTable(String table) { this.table = table; return this; } @@ -110,13 +110,13 @@ public Entity getEntity() { return entity; } - public MergeOperation setEntity(Entity entity) { + public MergeEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } - public class InsertOrReplaceOperation extends Operation { + public class InsertOrReplaceEntityOperation extends Operation { private String table; private Entity entity; @@ -124,7 +124,7 @@ public String getTable() { return table; } - public InsertOrReplaceOperation setTable(String table) { + public InsertOrReplaceEntityOperation setTable(String table) { this.table = table; return this; } @@ -133,13 +133,13 @@ public Entity getEntity() { return entity; } - public InsertOrReplaceOperation setEntity(Entity entity) { + public InsertOrReplaceEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } - public class InsertOrMergeOperation extends Operation { + public class InsertOrMergeEntityOperation extends Operation { private String table; private Entity entity; @@ -147,7 +147,7 @@ public String getTable() { return table; } - public InsertOrMergeOperation setTable(String table) { + public InsertOrMergeEntityOperation setTable(String table) { this.table = table; return this; } @@ -156,13 +156,13 @@ public Entity getEntity() { return entity; } - public InsertOrMergeOperation setEntity(Entity entity) { + public InsertOrMergeEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } - public class DeleteOperation extends Operation { + public class DeleteEntityOperation extends Operation { private String table; private String partitionKey; private String rowKey; @@ -171,7 +171,7 @@ public String getTable() { return table; } - public DeleteOperation setTable(String table) { + public DeleteEntityOperation setTable(String table) { this.table = table; return this; } @@ -180,7 +180,7 @@ public String getPartitionKey() { return partitionKey; } - public DeleteOperation setPartitionKey(String partitionKey) { + public DeleteEntityOperation setPartitionKey(String partitionKey) { this.partitionKey = partitionKey; return this; } @@ -189,7 +189,7 @@ public String getRowKey() { return rowKey; } - public DeleteOperation setRowKey(String rowKey) { + public DeleteEntityOperation setRowKey(String rowKey) { this.rowKey = rowKey; return this; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java index 462dc4d10ef63..30a0b3beebd94 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -1,5 +1,65 @@ package com.microsoft.windowsazure.services.table.models; +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.windowsazure.services.core.ServiceException; + public class BatchResult { + private List<Entry> entries = new ArrayList<Entry>(); + + public List<Entry> getEntries() { + return entries; + } + + public BatchResult setEntries(List<Entry> entries) { + this.entries = entries; + return this; + } + + public static abstract class Entry { + } + + public static class InsertEntity extends Entry { + private Entity entity; + + public Entity getEntity() { + return entity; + } + + public InsertEntity setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class UpdateEntity extends Entry { + private String etag; + + public String getEtag() { + return etag; + } + + public UpdateEntity setEtag(String etag) { + this.etag = etag; + return this; + } + } + + public static class DeleteEntity extends Entry { + + } + + public static class Error extends Entry { + private ServiceException error; + + public ServiceException getError() { + return error; + } + public Error setError(ServiceException error) { + this.error = error; + return this; + } + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index c489c675735ad..aadff98044810 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -30,6 +30,7 @@ import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.table.models.BatchOperations; import com.microsoft.windowsazure.services.table.models.BatchResult; +import com.microsoft.windowsazure.services.table.models.BatchResult.InsertEntity; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; @@ -650,5 +651,7 @@ public void batchWorks() throws Exception { // Assert assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(InsertEntity.class, result.getEntries().get(0).getClass()); } } From 34de8fec26ac99eac38bd4717d0f649fabefded1 Mon Sep 17 00:00:00 2001 From: Renaud Paquay <renaud.paquay@microsoft.com> Date: Fri, 13 Jan 2012 16:53:53 -0800 Subject: [PATCH 19/76] Additional test for "batch" operation --- .../table/TableServiceIntegrationTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index aadff98044810..8fd16a9e2bb6e 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -55,6 +55,7 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static String TEST_TABLE_4; private static String TEST_TABLE_5; private static String TEST_TABLE_6; + private static String TEST_TABLE_7; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; //private static String CREATABLE_TABLE_3; @@ -84,6 +85,7 @@ public static void setup() throws Exception { TEST_TABLE_4 = testTables[3]; TEST_TABLE_5 = testTables[4]; TEST_TABLE_6 = testTables[5]; + TEST_TABLE_7 = testTables[6]; CREATABLE_TABLE_1 = creatableTables[0]; CREATABLE_TABLE_2 = creatableTables[1]; @@ -654,4 +656,58 @@ public void batchWorks() throws Exception { assertEquals(1, result.getEntries().size()); assertEquals(InsertEntity.class, result.getEntries().get(0).getClass()); } + + @Test + public void batchMultipleWorks() throws Exception { + System.out.println("batchMultipleWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_7; + String partitionKey = "001"; + int insertCount = 100; + + // Act + BatchOperations batchOperations = new BatchOperations(); + for (int i = 0; i < insertCount; i++) { + + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchWorks-" + i) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + batchOperations.addInsertEntity(table, entity); + } + BatchResult result = service.batch(batchOperations); + + // Assert + assertNotNull(result); + assertEquals(insertCount, result.getEntries().size()); + for (int i = 0; i < insertCount; i++) { + assertEquals(InsertEntity.class, result.getEntries().get(i).getClass()); + + Entity entity = ((InsertEntity) result.getEntries().get(i)).getEntity(); + + assertEquals("001", entity.getPartitionKey()); + assertEquals("batchWorks-" + i, entity.getRowKey()); + assertNotNull(entity.getTimestamp()); + assertNotNull(entity.getEtag()); + + assertNotNull(entity.getProperty("test")); + assertEquals(true, entity.getProperty("test").getValue()); + + assertNotNull(entity.getProperty("test2")); + assertEquals("value", entity.getProperty("test2").getValue()); + + assertNotNull(entity.getProperty("test3")); + assertEquals(3, entity.getProperty("test3").getValue()); + + assertNotNull(entity.getProperty("test4")); + assertEquals(12345678901L, entity.getProperty("test4").getValue()); + + assertNotNull(entity.getProperty("test5")); + assertTrue(entity.getProperty("test5").getValue() instanceof Date); + } + } } From 18c825525a7b8782f92d383e5d1eaf303c6bf4eb Mon Sep 17 00:00:00 2001 From: Renaud Paquay <renaud.paquay@microsoft.com> Date: Sat, 14 Jan 2012 11:24:48 -0800 Subject: [PATCH 20/76] Simple code refactorings --- .../table/implementation/TableRestProxy.java | 91 +++++++++---------- .../table/models/BatchOperations.java | 17 ++-- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 944bb8214424a..a18ffe066a21e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -556,19 +556,61 @@ public BatchResult batch(BatchOperations operations, TableServiceOptions options WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); - MimeMultipart entity = createMimeMultipart(operations); + MimeMultipart entity = createBatchRequestBody(operations); builder = builder.type(entity.getContentType()); ClientResponse response = builder.post(ClientResponse.class, entity); ThrowIfError(response); BatchResult result = new BatchResult(); - result.setEntries(parseMimeMultipart(response, operations)); + result.setEntries(parseBatchResponse(response, operations)); return result; } - private List<Entry> parseMimeMultipart(ClientResponse response, BatchOperations operations) { + private MimeMultipart createBatchRequestBody(BatchOperations operations) { + List<DataSource> bodyPartContents = new ArrayList<DataSource>(); + int contentId = 1; + for (Operation operation : operations.getOperations()) { + + DataSource bodyPartContent = null; + // INSERT + if (operation instanceof InsertEntityOperation) { + InsertEntityOperation op = (InsertEntityOperation) operation; + + // + // Stream content into byte[] so that we have the length + // + InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); + byte[] bytes = inputStreamToByteArray(stream); + + // + // Create body of MIME part as the HTTP request + // + InternetHeaders headers = new InternetHeaders(); + headers.addHeader("Content-ID", Integer.toString(contentId++)); + headers.addHeader("Content-Type", "application/atom+xml;type=entry"); + headers.addHeader("Content-Length", Integer.toString(bytes.length)); + + //TODO: Review code to make sure encoding is correct + ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); + httpReaderWriter.appendMethod(httpRequest, "POST", channel.resource(url).path(op.getTable()).getURI()); + httpReaderWriter.appendHeaders(httpRequest, headers); + httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes)); + + bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), + "application/http"); + } + + if (bodyPartContent != null) { + bodyPartContents.add(bodyPartContent); + } + } + + return mimeReaderWriter.getMimeMultipart(bodyPartContents); + } + + private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations operations) { List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); @@ -627,48 +669,6 @@ else if (operation instanceof DeleteEntityOperation) { return result; } - private MimeMultipart createMimeMultipart(BatchOperations operations) { - List<DataSource> bodyPartContents = new ArrayList<DataSource>(); - int contentId = 1; - for (Operation operation : operations.getOperations()) { - - DataSource bodyPartContent = null; - // INSERT - if (operation instanceof InsertEntityOperation) { - InsertEntityOperation op = (InsertEntityOperation) operation; - - // - // Stream content into byte[] so that we have the length - // - InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); - byte[] bytes = inputStreamToByteArray(stream); - - // - // Create body of MIME part as the HTTP request - // - InternetHeaders headers = new InternetHeaders(); - headers.addHeader("Content-ID", Integer.toString(contentId++)); - headers.addHeader("Content-Type", "application/atom+xml;type=entry"); - headers.addHeader("Content-Length", Integer.toString(bytes.length)); - - //TODO: Review code to make sure encoding is correct - ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); - httpReaderWriter.appendMethod(httpRequest, "POST", channel.resource(url).path(op.getTable()).getURI()); - httpReaderWriter.appendHeaders(httpRequest, headers); - httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes)); - - bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), - "application/http"); - } - - if (bodyPartContent != null) { - bodyPartContents.add(bodyPartContent); - } - } - - return mimeReaderWriter.getMimeMultipart(bodyPartContents); - } - private byte[] inputStreamToByteArray(InputStream inputStream) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); @@ -691,5 +691,4 @@ private byte[] inputStreamToByteArray(InputStream inputStream) { throw new RuntimeException(e); } } - } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index 12eb2a98314e3..f60bfa8fb005f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -40,14 +40,15 @@ public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { } public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey) { - this.operations.add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); + this.operations + .add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); return this; } - public abstract class Operation { + public static abstract class Operation { } - public class InsertEntityOperation extends Operation { + public static class InsertEntityOperation extends Operation { private String table; private Entity entity; @@ -70,7 +71,7 @@ public InsertEntityOperation setEntity(Entity entity) { } } - public class UpdateEntityOperation extends Operation { + public static class UpdateEntityOperation extends Operation { private String table; private Entity entity; @@ -93,7 +94,7 @@ public UpdateEntityOperation setEntity(Entity entity) { } } - public class MergeEntityOperation extends Operation { + public static class MergeEntityOperation extends Operation { private String table; private Entity entity; @@ -116,7 +117,7 @@ public MergeEntityOperation setEntity(Entity entity) { } } - public class InsertOrReplaceEntityOperation extends Operation { + public static class InsertOrReplaceEntityOperation extends Operation { private String table; private Entity entity; @@ -139,7 +140,7 @@ public InsertOrReplaceEntityOperation setEntity(Entity entity) { } } - public class InsertOrMergeEntityOperation extends Operation { + public static class InsertOrMergeEntityOperation extends Operation { private String table; private Entity entity; @@ -162,7 +163,7 @@ public InsertOrMergeEntityOperation setEntity(Entity entity) { } } - public class DeleteEntityOperation extends Operation { + public static class DeleteEntityOperation extends Operation { private String table; private String partitionKey; private String rowKey; From 04cc6449fb846c8e8c73900ee6568799b21a2a23 Mon Sep 17 00:00:00 2001 From: Renaud Paquay <renaud.paquay@microsoft.com> Date: Sat, 14 Jan 2012 12:49:06 -0800 Subject: [PATCH 21/76] Support for all batch operations --- .../table/implementation/TableRestProxy.java | 121 ++++++++-- .../table/models/BatchOperations.java | 16 +- .../table/TableServiceIntegrationTest.java | 210 +++++++++++++++++- 3 files changed, 315 insertions(+), 32 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index a18ffe066a21e..096c5b4ec312e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -18,6 +18,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; @@ -574,32 +575,41 @@ private MimeMultipart createBatchRequestBody(BatchOperations operations) { for (Operation operation : operations.getOperations()) { DataSource bodyPartContent = null; - // INSERT if (operation instanceof InsertEntityOperation) { InsertEntityOperation op = (InsertEntityOperation) operation; - - // - // Stream content into byte[] so that we have the length - // - InputStream stream = atomReaderWriter.generateEntityEntry(op.getEntity()); - byte[] bytes = inputStreamToByteArray(stream); - - // - // Create body of MIME part as the HTTP request - // - InternetHeaders headers = new InternetHeaders(); - headers.addHeader("Content-ID", Integer.toString(contentId++)); - headers.addHeader("Content-Type", "application/atom+xml;type=entry"); - headers.addHeader("Content-Length", Integer.toString(bytes.length)); - - //TODO: Review code to make sure encoding is correct - ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); - httpReaderWriter.appendMethod(httpRequest, "POST", channel.resource(url).path(op.getTable()).getURI()); - httpReaderWriter.appendHeaders(httpRequest, headers); - httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes)); - - bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), - "application/http"); + bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "POST", + false/*includeEtag*/, contentId); + contentId++; + } + else if (operation instanceof UpdateEntityOperation) { + UpdateEntityOperation op = (UpdateEntityOperation) operation; + bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "PUT", + true/*includeEtag*/, contentId); + contentId++; + } + else if (operation instanceof MergeEntityOperation) { + MergeEntityOperation op = (MergeEntityOperation) operation; + bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "MERGE", + true/*includeEtag*/, contentId); + contentId++; + } + else if (operation instanceof InsertOrReplaceEntityOperation) { + InsertOrReplaceEntityOperation op = (InsertOrReplaceEntityOperation) operation; + bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "PUT", + false/*includeEtag*/, contentId); + contentId++; + } + else if (operation instanceof InsertOrMergeEntityOperation) { + InsertOrMergeEntityOperation op = (InsertOrMergeEntityOperation) operation; + bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "MERGE", + false/*includeEtag*/, contentId); + contentId++; + } + else if (operation instanceof DeleteEntityOperation) { + DeleteEntityOperation op = (DeleteEntityOperation) operation; + bodyPartContent = createBatchDeleteEntityPart(op.getTable(), op.getPartitionKey(), op.getRowKey(), + op.getEtag(), contentId); + contentId++; } if (bodyPartContent != null) { @@ -610,6 +620,69 @@ private MimeMultipart createBatchRequestBody(BatchOperations operations) { return mimeReaderWriter.getMimeMultipart(bodyPartContents); } + private DataSource createBatchInsertOrUpdateEntityPart(String table, Entity entity, String verb, + boolean includeEtag, int contentId) { + + URI path; + if ("POST".equals(verb)) { + path = channel.resource(url).path(table).getURI(); + } + else { + path = channel.resource(url).path(getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())) + .getURI(); + } + + // + // Stream content into byte[] so that we have the length + // + InputStream stream = atomReaderWriter.generateEntityEntry(entity); + byte[] bytes = inputStreamToByteArray(stream); + + // + // Create body of MIME part as the HTTP request + // + InternetHeaders headers = new InternetHeaders(); + headers.addHeader("Content-ID", Integer.toString(contentId)); + headers.addHeader("Content-Type", "application/atom+xml;type=entry"); + headers.addHeader("Content-Length", Integer.toString(bytes.length)); + if (includeEtag) { + headers.addHeader("If-Match", entity.getEtag()); + } + + //TODO: Review code to make sure encoding is correct + ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); + httpReaderWriter.appendMethod(httpRequest, verb, path); + httpReaderWriter.appendHeaders(httpRequest, headers); + httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes)); + + DataSource bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), + "application/http"); + return bodyPartContent; + } + + private DataSource createBatchDeleteEntityPart(String table, String partitionKey, String rowKey, String etag, + int contentId) { + + URI path = channel.resource(url).path(getEntityPath(table, partitionKey, rowKey)).getURI(); + + // + // Create body of MIME part as the HTTP request + // + InternetHeaders headers = new InternetHeaders(); + headers.addHeader("Content-ID", Integer.toString(contentId)); + headers.addHeader("If-Match", etag == null ? "*" : etag); + + //TODO: Review code to make sure encoding is correct + ByteArrayOutputStream httpRequest = new ByteArrayOutputStream(); + httpReaderWriter.appendMethod(httpRequest, "DELETE", path); + httpReaderWriter.appendHeaders(httpRequest, headers); + httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(new byte[0])); + + DataSource bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()), + "application/http"); + return bodyPartContent; + } + private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations operations) { List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index f60bfa8fb005f..f1df873ac4da0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -39,9 +39,9 @@ public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { return this; } - public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey) { - this.operations - .add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)); + public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey, String etag) { + this.operations.add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey) + .setEtag(etag)); return this; } @@ -167,6 +167,7 @@ public static class DeleteEntityOperation extends Operation { private String table; private String partitionKey; private String rowKey; + private String etag; public String getTable() { return table; @@ -194,5 +195,14 @@ public DeleteEntityOperation setRowKey(String rowKey) { this.rowKey = rowKey; return this; } + + public String getEtag() { + return etag; + } + + public DeleteEntityOperation setEtag(String etag) { + this.etag = etag; + return this; + } } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 8fd16a9e2bb6e..c8143349741d4 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -30,7 +30,9 @@ import com.microsoft.windowsazure.services.core.ServiceException; import com.microsoft.windowsazure.services.table.models.BatchOperations; import com.microsoft.windowsazure.services.table.models.BatchResult; +import com.microsoft.windowsazure.services.table.models.BatchResult.DeleteEntity; import com.microsoft.windowsazure.services.table.models.BatchResult.InsertEntity; +import com.microsoft.windowsazure.services.table.models.BatchResult.UpdateEntity; import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions; import com.microsoft.windowsazure.services.table.models.EdmType; import com.microsoft.windowsazure.services.table.models.Entity; @@ -56,6 +58,7 @@ public class TableServiceIntegrationTest extends IntegrationTestBase { private static String TEST_TABLE_5; private static String TEST_TABLE_6; private static String TEST_TABLE_7; + private static String TEST_TABLE_8; private static String CREATABLE_TABLE_1; private static String CREATABLE_TABLE_2; //private static String CREATABLE_TABLE_3; @@ -86,6 +89,7 @@ public static void setup() throws Exception { TEST_TABLE_5 = testTables[4]; TEST_TABLE_6 = testTables[5]; TEST_TABLE_7 = testTables[6]; + TEST_TABLE_8 = testTables[7]; CREATABLE_TABLE_1 = creatableTables[0]; CREATABLE_TABLE_2 = creatableTables[1]; @@ -635,20 +639,21 @@ public void queryEntitiesWithFilterWorks() throws Exception { } @Test - public void batchWorks() throws Exception { - System.out.println("batchWorks()"); + public void batchInsertWorks() throws Exception { + System.out.println("batchInsertWorks()"); // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); String table = TEST_TABLE_6; String partitionKey = "001"; - Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchWorks") + + // Act + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) .setProperty("test5", EdmType.DATETIME, new Date()); - // Act BatchResult result = service.batch(new BatchOperations().addInsertEntity(table, entity)); // Assert @@ -658,7 +663,129 @@ public void batchWorks() throws Exception { } @Test - public void batchMultipleWorks() throws Exception { + public void batchUpdateWorks() throws Exception { + System.out.println("batchUpdateWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchUpdateWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + entity = service.insertEntity(table, entity).getEntity(); + + // Act + entity.setProperty("test", EdmType.BOOLEAN, false); + BatchResult result = service.batch(new BatchOperations().addUpdateEntity(table, entity)); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + public void batchMergeWorks() throws Exception { + System.out.println("batchMergeWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchMergeWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + entity = service.insertEntity(table, entity).getEntity(); + + // Act + BatchResult result = service.batch(new BatchOperations().addMergeEntity(table, entity)); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + public void batchInsertOrReplaceWorks() throws Exception { + System.out.println("batchInsertOrReplaceWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + + // Act + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertOrReplaceWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + BatchResult result = service.batch(new BatchOperations().addInsertOrReplaceEntity(table, entity)); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + public void batchInsertOrMergeWorks() throws Exception { + System.out.println("batchInsertOrMergeWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + + // Act + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertOrMergeWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + BatchResult result = service.batch(new BatchOperations().addInsertOrMergeEntity(table, entity)); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + public void batchDeleteWorks() throws Exception { + System.out.println("batchDeleteWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_6; + String partitionKey = "001"; + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchDeleteWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + entity = service.insertEntity(table, entity).getEntity(); + + // Act + BatchResult result = service.batch(new BatchOperations().addDeleteEntity(table, entity.getPartitionKey(), + entity.getRowKey(), entity.getEtag())); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(DeleteEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + public void batchLotsOfInsertsWorks() throws Exception { System.out.println("batchMultipleWorks()"); // Arrange @@ -710,4 +837,77 @@ public void batchMultipleWorks() throws Exception { assertTrue(entity.getProperty("test5").getValue() instanceof Date); } } + + @Test + public void batchAllOperationsTogetherWorks() throws Exception { + System.out.println("batchAllOperationsWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_8; + String partitionKey = "001"; + + // Insert a few entities to allow updating them in batch + Entity entity1 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 1) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + entity1 = service.insertEntity(table, entity1).getEntity(); + + Entity entity2 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 2) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + entity2 = service.insertEntity(table, entity2).getEntity(); + + Entity entity3 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 3) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + entity3 = service.insertEntity(table, entity3).getEntity(); + + Entity entity4 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 4) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + + entity4 = service.insertEntity(table, entity4).getEntity(); + + // Act + BatchOperations batchOperations = new BatchOperations(); + + Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks") + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + batchOperations.addInsertEntity(table, entity); + + batchOperations.addDeleteEntity(table, entity1.getPartitionKey(), entity1.getRowKey(), entity1.getEtag()); + + batchOperations.addUpdateEntity(table, entity2.setProperty("test", EdmType.INT32, 5)); + batchOperations.addMergeEntity(table, entity3.setProperty("test", EdmType.INT32, 5)); + batchOperations.addInsertOrReplaceEntity(table, entity4.setProperty("test", EdmType.INT32, 5)); + + Entity entity5 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 5) + .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") + .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) + .setProperty("test5", EdmType.DATETIME, new Date()); + batchOperations.addInsertOrMergeEntity(table, entity5); + + BatchResult result = service.batch(batchOperations); + + // Assert + assertNotNull(result); + assertEquals(batchOperations.getOperations().size(), result.getEntries().size()); + assertEquals(InsertEntity.class, result.getEntries().get(0).getClass()); + assertEquals(DeleteEntity.class, result.getEntries().get(1).getClass()); + assertEquals(UpdateEntity.class, result.getEntries().get(2).getClass()); + assertEquals(UpdateEntity.class, result.getEntries().get(3).getClass()); + assertEquals(UpdateEntity.class, result.getEntries().get(4).getClass()); + assertEquals(UpdateEntity.class, result.getEntries().get(5).getClass()); + } } From 87ebda2335c57b0e44b193021ecf4ea51bf5ca29 Mon Sep 17 00:00:00 2001 From: Louis DeJardin <lodejard@microsoft.com> Date: Mon, 13 Feb 2012 15:41:27 -0800 Subject: [PATCH 22/76] Commenting out an optional diagnostic line --- .../windowsazure/services/core/utils/pipeline/Exports.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java index f7aae2cd9188f..234581b023fb8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java @@ -22,7 +22,6 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.client.filter.LoggingFilter; public class Exports implements Builder.Exports { @@ -53,7 +52,7 @@ public Client create(String profile, Builder builder, Map<String, Object> proper public HttpURLConnectionClient create(String profile, Builder builder, Map<String, Object> properties) { ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig"); HttpURLConnectionClient client = HttpURLConnectionClient.create(clientConfig); - client.addFilter(new LoggingFilter()); + //client.addFilter(new LoggingFilter()); return client; } }); From 3a7d97c495f92ac8b0d13643f254ca912eb43c64 Mon Sep 17 00:00:00 2001 From: Joe Giardino <joegiard@microsoft.com> Date: Wed, 22 Feb 2012 13:23:01 -0800 Subject: [PATCH 23/76] =?UTF-8?q?Table=20Client=20commit=20=E2=80=A2=09the?= =?UTF-8?q?=20table=20client=20=E2=80=A2=09relevant=20core=20updates=20?= =?UTF-8?q?=E2=80=A2=09table=20tests=20=E2=80=A2=09pom.xml=20is=20updated?= =?UTF-8?q?=20to=20reference=20apache=20commons=20lang=20=E2=80=A2=09Updat?= =?UTF-8?q?ing=20useragent=20to=20v1.1.2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- microsoft-azure-api/pom.xml | 5 + .../core/storage/AccessCondition.java | 9 +- .../core/storage/CloudStorageAccount.java | 21 + .../services/core/storage/Constants.java | 2 +- .../services/core/storage/ResultSegment.java | 10 +- .../services/core/storage/ServiceClient.java | 10 +- .../core/storage/StorageCredentials.java | 8 +- ...orageCredentialsSharedAccessSignature.java | 8 +- .../core/storage/StorageErrorCodeStrings.java | 5 + .../utils/implementation/ExecutionEngine.java | 13 + .../implementation/StorageErrorResponse.java | 1 + .../services/table/client/AtomPubParser.java | 562 +++++++ .../table/client/CloudTableClient.java | 1359 +++++++++++++++++ .../table/client/DynamicTableEntity.java | 104 ++ .../services/table/client/EdmType.java | 203 +++ .../services/table/client/EntityProperty.java | 496 ++++++ .../services/table/client/EntityResolver.java | 61 + .../services/table/client/Ignore.java | 37 + .../services/table/client/MimeHeader.java | 25 + .../services/table/client/MimeHelper.java | 510 +++++++ .../services/table/client/MimePart.java | 28 + .../services/table/client/ODataConstants.java | 183 +++ .../services/table/client/ODataPayload.java | 42 + .../services/table/client/PropertyPair.java | 277 ++++ .../table/client/QueryTableOperation.java | 268 ++++ .../services/table/client/StoreAs.java | 49 + .../table/client/TableBatchOperation.java | 514 +++++++ .../services/table/client/TableConstants.java | 143 ++ .../services/table/client/TableEntity.java | 170 +++ .../services/table/client/TableOperation.java | 699 +++++++++ .../table/client/TableOperationType.java | 43 + .../services/table/client/TableQuery.java | 773 ++++++++++ .../services/table/client/TableRequest.java | 432 ++++++ .../table/client/TableRequestOptions.java | 35 + .../services/table/client/TableResponse.java | 75 + .../services/table/client/TableResult.java | 179 +++ .../table/client/TableServiceEntity.java | 414 +++++ .../table/client/TableServiceException.java | 172 +++ .../table/client/TableUpdateType.java | 31 + .../client/TableBatchOperationTests.java | 762 +++++++++ .../table/client/TableClientTests.java | 268 ++++ .../table/client/TableEscapingTests.java | 231 +++ .../table/client/TableOperationTests.java | 591 +++++++ .../table/client/TableQueryTests.java | 427 ++++++ .../table/client/TableSerializerTests.java | 269 ++++ .../services/table/client/TableTestBase.java | 599 ++++++++ 46 files changed, 11099 insertions(+), 24 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/AtomPubParser.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/DynamicTableEntity.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EdmType.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityProperty.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityResolver.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/Ignore.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHeader.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHelper.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimePart.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataConstants.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataPayload.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/PropertyPair.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/StoreAs.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableConstants.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableEntity.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperationType.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequestOptions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResponse.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResult.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceException.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableUpdateType.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableBatchOperationTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableOperationTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableSerializerTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index 3e7dec871f279..d9b0c4c4f2cf0 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -85,6 +85,11 @@ <artifactId>mail</artifactId> <version>1.4</version> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.1</version> + </dependency> </dependencies> <build> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessCondition.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessCondition.java index 970cc3850ead5..c2a741ef3ed2d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessCondition.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessCondition.java @@ -40,9 +40,8 @@ public static AccessCondition generateEmptyCondition() { * Returns an access condition such that an operation will be performed only if the resource's ETag value matches * the specified ETag value. * <p> - * Setting this access condition modifies the request to include the HTTP <i>If-Match</i> conditional header. If - * this access condition is set, the operation is performed only if the ETag of the resource matches the specified - * ETag. + * Setting this access condition modifies the request to include the HTTP <i>If-Match</i> conditional header. If this + * access condition is set, the operation is performed only if the ETag of the resource matches the specified ETag. * <p> * For more information, see <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>Specifying * Conditional Headers for Blob Service Operations</a>. @@ -84,8 +83,8 @@ public static AccessCondition generateIfModifiedSinceCondition(final Date lastMo * Returns an access condition such that an operation will be performed only if the resource's ETag value does not * match the specified ETag value. * <p> - * Setting this access condition modifies the request to include the HTTP <i>If-None-Match</i> conditional header. - * If this access condition is set, the operation is performed only if the ETag of the resource does not match the + * Setting this access condition modifies the request to include the HTTP <i>If-None-Match</i> conditional header. If + * this access condition is set, the operation is performed only if the ETag of the resource does not match the * specified ETag. * <p> * For more information, see <a href= 'http://go.microsoft.com/fwlink/?LinkID=224642&clcid=0x409'>Specifying diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/CloudStorageAccount.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/CloudStorageAccount.java index 8818215a944ee..8beb56163e4cc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/CloudStorageAccount.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/CloudStorageAccount.java @@ -24,6 +24,7 @@ import com.microsoft.windowsazure.services.blob.client.CloudBlobClient; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.queue.client.CloudQueueClient; +import com.microsoft.windowsazure.services.table.client.CloudTableClient; /** * Represents a Windows Azure storage account. @@ -538,6 +539,26 @@ public CloudQueueClient createCloudQueueClient() { return new CloudQueueClient(this.getQueueEndpoint(), this.getCredentials()); } + /** + * Creates a new table service client. + * + * @return A client object that uses the Table service endpoint. + */ + public CloudTableClient createCloudTableClient() { + if (this.getTableEndpoint() == null) { + throw new IllegalArgumentException("No table endpoint configured."); + } + + if (this.credentials == null) { + throw new IllegalArgumentException("No credentials provided."); + } + + if (!this.credentials.canCredentialsSignRequest()) { + throw new IllegalArgumentException("CloudTableClient requires a credential that can sign request"); + } + return new CloudTableClient(this.getTableEndpoint(), this.getCredentials()); + } + /** * Returns the endpoint for the Blob service, as configured for the storage account. This method is not supported * when using shared access signature credentials. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java index 492320796e708..7676efe058daa 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java @@ -287,7 +287,7 @@ public static class HeaderConstants { /** * Specifies the value to use for UserAgent header. */ - public static final String USER_AGENT_VERSION = "Client v0.1.1"; + public static final String USER_AGENT_VERSION = "Client v0.1.2"; } /** diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ResultSegment.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ResultSegment.java index debb82c4c6a89..39d2c8b737068 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ResultSegment.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ResultSegment.java @@ -39,9 +39,9 @@ public class ResultSegment<T> { private final int pageSize; /** - * Holds the Iterable collection of results. + * Holds the ArrayList of results. */ - private final Iterable<T> results; + private final ArrayList<T> results; /** * Reserved for internal use. Creates an instance of the <code>ResultSegment</code> class. @@ -115,11 +115,11 @@ public int getRemainingPageResults() { } /** - * Returns an enumerable set of results from the blob service. + * Returns an enumerable set of results from the service. * - * @return The results retrieved from the blob service. + * @return The results retrieved from the service. */ - public Iterable<T> getResults() { + public ArrayList<T> getResults() { return this.results; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ServiceClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ServiceClient.java index e9d0b3f6ad373..baa4c1db04659 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ServiceClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/ServiceClient.java @@ -242,13 +242,13 @@ public void setRetryPolicyFactory(final RetryPolicyFactory retryPolicyFactory) { /** * Sets the timeout to use when making requests to the storage service. * <p> - * The server timeout interval begins at the time that the complete request has been received by the service, and - * the server begins processing the response. If the timeout interval elapses before the response is returned to the + * The server timeout interval begins at the time that the complete request has been received by the service, and the + * server begins processing the response. If the timeout interval elapses before the response is returned to the * client, the operation times out. The timeout interval resets with each retry, if the request is retried. * - * The default timeout interval for a request made via the service client is 90 seconds. You can change this value - * on the service client by setting this property, so that all subsequent requests made via the service client will - * use the new timeout interval. You can also change this value for an individual request, by setting the + * The default timeout interval for a request made via the service client is 90 seconds. You can change this value on + * the service client by setting this property, so that all subsequent requests made via the service client will use + * the new timeout interval. You can also change this value for an individual request, by setting the * {@link RequestOptions#timeoutIntervalInMs} property. * * If you are downloading a large blob, you should increase the value of the timeout beyond the default value. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentials.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentials.java index 84d4c055976ac..84b4c194ac8fb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentials.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentials.java @@ -39,8 +39,8 @@ public abstract class StorageCredentials { * Either include an account name with an account key (specifying values for * {@link CloudStorageAccount#ACCOUNT_NAME_NAME} and {@link CloudStorageAccount#ACCOUNT_KEY_NAME} ), or a * shared access signature (specifying a value for - * {@link CloudStorageAccount#SHARED_ACCESS_SIGNATURE_NAME} ). If you use an account name and account - * key, do not include a shared access signature, and vice versa. + * {@link CloudStorageAccount#SHARED_ACCESS_SIGNATURE_NAME} ). If you use an account name and account key, + * do not include a shared access signature, and vice versa. * * @return A {@link StorageCredentials} object representing the storage credentials determined from the name/value * pairs. @@ -81,8 +81,8 @@ protected static StorageCredentials tryParseCredentials(final HashMap<String, St * @param connectionString * A <code>String</code> that contains the key/value pairs that represent the storage credentials. * <p> - * The format for the connection string is in the pattern "<i>keyname=value</i>". Multiple key/value - * pairs can be separated by a semi-colon, for example, "<i>keyname1=value1;keyname2=value2</i>". + * The format for the connection string is in the pattern "<i>keyname=value</i>". Multiple key/value pairs + * can be separated by a semi-colon, for example, "<i>keyname1=value1;keyname2=value2</i>". * * @return A {@link StorageCredentials} object representing the storage credentials determined from the connection * string. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsSharedAccessSignature.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsSharedAccessSignature.java index c33ce78d64590..2f3e44a380ed1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsSharedAccessSignature.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsSharedAccessSignature.java @@ -96,8 +96,8 @@ public String computeHmac256(final String value) { /** * Computes a signature for the specified string using the HMAC-SHA256 algorithm with the specified operation - * context. This is not a valid operation for objects of type <code>StorageCredentialsSharedAccessSignature</code> - * so the method merely returns <code>null</code>. + * context. This is not a valid operation for objects of type <code>StorageCredentialsSharedAccessSignature</code> so + * the method merely returns <code>null</code>. * * @param value * The UTF-8-encoded string to sign. @@ -130,8 +130,8 @@ public String computeHmac512(final String value) { /** * Computes a signature for the specified string using the HMAC-SHA512 algorithm with the specified operation - * context. This is not a valid operation for objects of type <code>StorageCredentialsSharedAccessSignature</code> - * so the method merely returns <code>null</code>. + * context. This is not a valid operation for objects of type <code>StorageCredentialsSharedAccessSignature</code> so + * the method merely returns <code>null</code>. * * @param value * The UTF-8-encoded string to sign. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCodeStrings.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCodeStrings.java index 5e37420554264..624a31467311d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCodeStrings.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCodeStrings.java @@ -188,6 +188,11 @@ public final class StorageErrorCodeStrings { */ public static final String SERVER_BUSY = "ServerBusy"; + /** + * Table Already Exists + */ + public static final String TABLE_ALREADY_EXISTS = "TableAlreadyExists"; + /** * One or more header values are not supported. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/ExecutionEngine.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/ExecutionEngine.java index 02e3554479058..e30d5f1e45440 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/ExecutionEngine.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/ExecutionEngine.java @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.microsoft.windowsazure.services.core.storage.utils.implementation; import java.io.IOException; @@ -34,6 +35,7 @@ import com.microsoft.windowsazure.services.core.storage.RetryResult; import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.table.client.TableServiceException; /** * RESERVED FOR INTERNAL USE. A class that handles execution of StorageOperations and enforces retry policies. @@ -166,6 +168,17 @@ public static <CLIENT_TYPE, PARENT_TYPE, RESULT_TYPE> RESULT_TYPE executeWithRet setLastException(opContext, translatedException); throw translatedException; } + catch (final TableServiceException e) { + task.getResult().setStatusCode(e.getHttpStatusCode()); + task.getResult().setStatusMessage(e.getMessage()); + setLastException(opContext, e); + if (!e.isRetryable()) { + throw e; + } + else { + translatedException = e; + } + } catch (final StorageException e) { // Non Retryable, just throw // do not translate StorageException diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/StorageErrorResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/StorageErrorResponse.java index 049bf75757fd5..90a2713c57935 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/StorageErrorResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/StorageErrorResponse.java @@ -12,6 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.microsoft.windowsazure.services.core.storage.utils.implementation; import java.io.InputStream; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/AtomPubParser.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/AtomPubParser.java new file mode 100644 index 0000000000000..4b462cb334137 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/AtomPubParser.java @@ -0,0 +1,562 @@ +/** + * Copyright 2011 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.OutputStream; +import java.text.ParseException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.apache.commons.lang3.StringEscapeUtils; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * Reserved for internal use. A class used to read and write Table entities in OData AtomPub format requests and + * responses. + * <p> + * For more information about OData, see the <a href="http://www.odata.org/">Open Data Protocol</a> website. For more + * information about the AtomPub format used in OData, see <a + * href="http://www.odata.org/developers/protocols/atom-format">OData Protocol Atom Format</a>. + */ +class AtomPubParser { + /** + * Reserved for internal use. A static factory method to construct an <code>XMLStreamWriter</code> instance based on + * the specified <code>OutputStream</code>. + * + * @param outStream + * The <code>OutputStream</code> instance to create an <code>XMLStreamWriter</code> on. + * @return + * An <code>XMLStreamWriter</code> instance based on the specified <code>OutputStream</code>. + * @throws XMLStreamException + * if an error occurs while creating the stream. + */ + protected static XMLStreamWriter generateTableWriter(final OutputStream outStream) throws XMLStreamException { + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + return xmlOutFactoryInst.createXMLStreamWriter(outStream, "UTF-8"); + } + + /** + * Reserved for internal use. Parses the operation response as an entity. Parses the result returned in the + * specified stream in AtomPub format into a {@link TableResult} containing an entity of the specified class type + * projected using the specified resolver. + * + * @param xmlr + * An <code>XMLStreamReader</code> on the input stream. + * @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 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 XMLStreamException + * if an error occurs while accessing the stream. + * @throws ParseException + * if an error occurs while parsing 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. + */ + protected static <T extends TableEntity, R> TableResult parseEntity(final XMLStreamReader xmlr, + final Class<T> clazzType, final EntityResolver<R> resolver, final OperationContext opContext) + throws XMLStreamException, ParseException, InstantiationException, IllegalAccessException, StorageException { + int eventType = xmlr.getEventType(); + final TableResult res = new TableResult(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.ENTRY); + + res.setEtag(StringEscapeUtils.unescapeHtml4(xmlr.getAttributeValue(ODataConstants.DATA_SERVICES_METADATA_NS, + ODataConstants.ETAG))); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + if (eventType == XMLStreamConstants.CHARACTERS) { + xmlr.getText(); + continue; + } + + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT) { + if (name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.ID)) { + res.setId(Utility.readElementFromXMLReader(xmlr, ODataConstants.ID)); + } + else if (name.equals(ODataConstants.BRACKETED_DATA_SERVICES_METADATA_NS + ODataConstants.PROPERTIES)) { + // Do read properties + if (resolver == null && clazzType == null) { + return res; + } + else { + res.setProperties(readProperties(xmlr, opContext)); + break; + } + } + } + } + + // Move to end Content + eventType = xmlr.next(); + if (eventType == XMLStreamConstants.CHARACTERS) { + eventType = xmlr.next(); + } + xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.CONTENT); + + eventType = xmlr.next(); + if (eventType == XMLStreamConstants.CHARACTERS) { + eventType = xmlr.next(); + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.ENTRY); + + String rowKey = null; + String partitionKey = null; + Date timestamp = null; + + // Remove core properties from map and set individually + EntityProperty tempProp = res.getProperties().get(TableConstants.PARTITION_KEY); + if (tempProp != null) { + res.getProperties().remove(TableConstants.PARTITION_KEY); + partitionKey = tempProp.getValueAsString(); + } + + tempProp = res.getProperties().get(TableConstants.ROW_KEY); + if (tempProp != null) { + res.getProperties().remove(TableConstants.ROW_KEY); + rowKey = tempProp.getValueAsString(); + } + + tempProp = res.getProperties().get(TableConstants.TIMESTAMP); + if (tempProp != null) { + res.getProperties().remove(TableConstants.TIMESTAMP); + timestamp = tempProp.getValueAsDate(); + } + + if (resolver != null) { + // Call resolver + 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; + } + + /** + * 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 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 XMLStreamException + * if an error occurs while accessing the stream. + * @throws ParseException + * if an error occurs while parsing 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. + */ + @SuppressWarnings("unchecked") + protected static <T extends TableEntity, R> ODataPayload<?> parseResponse(final InputStream inStream, + final Class<T> clazzType, final EntityResolver<R> resolver, final OperationContext opContext) + throws XMLStreamException, ParseException, InstantiationException, IllegalAccessException, StorageException { + ODataPayload<T> corePayload = null; + ODataPayload<R> resolvedPayload = null; + ODataPayload<?> commonPayload = null; + + if (resolver != null) { + resolvedPayload = new ODataPayload<R>(); + commonPayload = resolvedPayload; + } + else { + corePayload = new ODataPayload<T>(); + commonPayload = corePayload; + } + + final XMLStreamReader xmlr = Utility.createXMLStreamReaderFromStream(inStream); + int eventType = xmlr.getEventType(); + xmlr.require(XMLStreamConstants.START_DOCUMENT, null, null); + eventType = xmlr.next(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.FEED); + // skip feed chars + eventType = xmlr.next(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.CHARACTERS) { + xmlr.getText(); + continue; + } + + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT) { + if (name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.ENTRY)) { + final TableResult res = parseEntity(xmlr, clazzType, resolver, opContext); + if (corePayload != null) { + corePayload.tableResults.add(res); + } + + if (resolver != null) { + resolvedPayload.results.add((R) res.getResult()); + } + else { + corePayload.results.add((T) res.getResult()); + } + } + } + else if (eventType == XMLStreamConstants.END_ELEMENT + && name.equals(ODataConstants.BRACKETED_ATOM_NS + ODataConstants.FEED)) { + break; + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.FEED); + return commonPayload; + } + + /** + * Reserved for internal use. Parses the operation response as an entity. Reads entity data from the specified + * <code>XMLStreamReader</code> using the specified class type and optionally projects the entity result with the + * specified resolver into a {@link TableResult} object. + * + * @param xmlr + * The <code>XMLStreamReader</code> to read the data to parse from. + * @param httpStatusCode + * The HTTP status code returned with the operation response. + * @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 entitys as instance of the class type <code>T</code>. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * @return + * A {@link TableResult} object with the parsed operation response. + * + * @throws XMLStreamException + * if an error occurs while accessing the stream. + * @throws ParseException + * if an error occurs while parsing 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. + */ + protected static <T extends TableEntity, R> TableResult parseSingleOpResponse(final XMLStreamReader xmlr, + final int httpStatusCode, final Class<T> clazzType, final EntityResolver<R> resolver, + final OperationContext opContext) throws XMLStreamException, ParseException, InstantiationException, + IllegalAccessException, StorageException { + xmlr.require(XMLStreamConstants.START_DOCUMENT, null, null); + xmlr.next(); + + final TableResult res = parseEntity(xmlr, clazzType, resolver, opContext); + res.setHttpStatusCode(httpStatusCode); + return res; + } + + /** + * Reserved for internal use. Reads the properties of an entity from the stream into a map of property names to + * typed values. Reads the entity data as an AtomPub Entry Resource from the specified {@link XMLStreamReader} into + * a map of <code>String</code> property names to {@link EntityProperty} data typed values. + * + * @param xmlr + * The <code>XMLStreamReader</code> to read the data from. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * + * @return + * A <code>java.util.HashMap</code> containing a map of <code>String</code> property names to + * {@link EntityProperty} data typed values found in the entity data. + * @throws XMLStreamException + * if an error occurs accessing the stream. + * @throws ParseException + * if an error occurs converting the input to a particular data type. + */ + protected static HashMap<String, EntityProperty> readProperties(final XMLStreamReader xmlr, + final OperationContext opContext) throws XMLStreamException, ParseException { + int eventType = xmlr.getEventType(); + xmlr.require(XMLStreamConstants.START_ELEMENT, null, ODataConstants.PROPERTIES); + final HashMap<String, EntityProperty> properties = new HashMap<String, EntityProperty>(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + if (eventType == XMLStreamConstants.CHARACTERS) { + xmlr.getText(); + continue; + } + + if (eventType == XMLStreamConstants.START_ELEMENT + && xmlr.getNamespaceURI().equals(ODataConstants.DATA_SERVICES_NS)) { + final String key = xmlr.getLocalName(); + String val = Constants.EMPTY_STRING; + String edmType = null; + + if (xmlr.getAttributeCount() > 0) { + edmType = xmlr.getAttributeValue(ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE); + } + + // move to chars + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.CHARACTERS) { + val = xmlr.getText(); + + // end element + eventType = xmlr.next(); + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, key); + + final EntityProperty newProp = new EntityProperty(val, EdmType.parse(edmType)); + properties.put(key, newProp); + } + else if (eventType == XMLStreamConstants.END_ELEMENT + && xmlr.getName().toString() + .equals(ODataConstants.BRACKETED_DATA_SERVICES_METADATA_NS + ODataConstants.PROPERTIES)) { + // End read properties + break; + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, ODataConstants.PROPERTIES); + return properties; + } + + /** + * Reserved for internal use. Writes an entity to the stream as an AtomPub Entry Resource, leaving the stream open + * for additional writing. + * + * @param entity + * The instance implementing {@link TableEntity} to write to the output stream. + * @param isTableEntry + * A flag indicating the entity is a reference to a table at the top level of the storage service when + * <code>true<code> and a reference to an entity within a table when <code>false</code>. + * @param xmlw + * The <code>XMLStreamWriter</code> to write the entity to. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * + * @throws XMLStreamException + * if an error occurs accessing the stream. + * @throws StorageException + * if a Storage service error occurs. + */ + protected static void writeEntityToStream(final TableEntity entity, final boolean isTableEntry, + final XMLStreamWriter xmlw, final OperationContext opContext) throws XMLStreamException, StorageException { + final HashMap<String, EntityProperty> properties = entity.writeEntity(opContext); + if (properties == null) { + throw new IllegalArgumentException("Entity did not produce properties to serialize"); + } + + if (!isTableEntry) { + Utility.assertNotNullOrEmpty(TableConstants.PARTITION_KEY, entity.getPartitionKey()); + Utility.assertNotNullOrEmpty(TableConstants.ROW_KEY, entity.getRowKey()); + Utility.assertNotNull(TableConstants.TIMESTAMP, entity.getTimestamp()); + } + + // Begin entry + xmlw.writeStartElement("entry"); + xmlw.writeNamespace("d", ODataConstants.DATA_SERVICES_NS); + xmlw.writeNamespace("m", ODataConstants.DATA_SERVICES_METADATA_NS); + + // default namespace + xmlw.writeNamespace(null, ODataConstants.ATOM_NS); + + // Content + xmlw.writeStartElement(ODataConstants.CONTENT); + xmlw.writeAttribute(ODataConstants.TYPE, ODataConstants.ODATA_CONTENT_TYPE); + + // m:properties + xmlw.writeStartElement("m", ODataConstants.PROPERTIES, ODataConstants.DATA_SERVICES_METADATA_NS); + + if (!isTableEntry) { + // d:PartitionKey + xmlw.writeStartElement("d", TableConstants.PARTITION_KEY, ODataConstants.DATA_SERVICES_NS); + xmlw.writeAttribute("xml", "xml", "space", "preserve"); + xmlw.writeCharacters(entity.getPartitionKey()); + xmlw.writeEndElement(); + + // d:RowKey + xmlw.writeStartElement("d", TableConstants.ROW_KEY, ODataConstants.DATA_SERVICES_NS); + xmlw.writeAttribute("xml", "xml", "space", "preserve"); + xmlw.writeCharacters(entity.getRowKey()); + xmlw.writeEndElement(); + + // d:Timestamp + if (entity.getTimestamp() == null) { + entity.setTimestamp(new Date()); + } + + xmlw.writeStartElement("d", TableConstants.TIMESTAMP, ODataConstants.DATA_SERVICES_NS); + xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE, + EdmType.DATE_TIME.toString()); + xmlw.writeCharacters(Utility.getTimeByZoneAndFormat(entity.getTimestamp(), Utility.UTC_ZONE, + Utility.ISO8061_LONG_PATTERN)); + xmlw.writeEndElement(); + } + + for (final Entry<String, EntityProperty> ent : properties.entrySet()) { + if (ent.getKey().equals(TableConstants.PARTITION_KEY) || ent.getKey().equals(TableConstants.ROW_KEY) + || ent.getKey().equals(TableConstants.TIMESTAMP) || ent.getKey().equals("Etag")) { + continue; + } + + EntityProperty currProp = ent.getValue(); + + // d:PropName + xmlw.writeStartElement("d", ent.getKey(), ODataConstants.DATA_SERVICES_NS); + + if (currProp.getEdmType() == EdmType.STRING) { + xmlw.writeAttribute("xml", "xml", "space", "preserve"); + } + else if (currProp.getEdmType().toString().length() != 0) { + String edmTypeString = currProp.getEdmType().toString(); + if (edmTypeString.length() != 0) { + xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.TYPE, + edmTypeString); + } + } + + if (currProp.getIsNull()) { + xmlw.writeAttribute("m", ODataConstants.DATA_SERVICES_METADATA_NS, ODataConstants.NULL, Constants.TRUE); + } + + // Write Value + xmlw.writeCharacters(currProp.getValueAsString()); + // End d:PropName + xmlw.writeEndElement(); + } + + // End m:properties + xmlw.writeEndElement(); + + // End content + xmlw.writeEndElement(); + + // End entry + xmlw.writeEndElement(); + } + + /** + * Reserved for internal use. Writes a single entity to the specified <code>OutputStream</code> as a complete XML + * document. + * + * @param entity + * The instance implementing {@link TableEntity} to write to the output stream. + * @param isTableEntry + * A flag indicating the entity is a reference to a table at the top level of the storage service when + * <code>true<code> and a reference to an entity within a table when <code>false</code>. + * @param outStream + * The <code>OutputStream</code> to write the entity to. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * + * @throws XMLStreamException + * if an error occurs creating or accessing the stream. + * @throws StorageException + * if a Storage service error occurs. + */ + protected static void writeSingleEntityToStream(final TableEntity entity, final boolean isTableEntry, + final OutputStream outStream, final OperationContext opContext) throws XMLStreamException, StorageException { + final XMLStreamWriter xmlw = AtomPubParser.generateTableWriter(outStream); + writeSingleEntityToStream(entity, isTableEntry, xmlw, opContext); + } + + /** + * Reserved for internal use. Writes a single entity to the specified <code>XMLStreamWriter</code> as a complete XML + * document. + * + * @param entity + * The instance implementing {@link TableEntity} to write to the output stream. + * @param isTableEntry + * A flag indicating the entity is a reference to a table at the top level of the storage service when + * <code>true<code> and a reference to an entity within a table when <code>false</code>. + * @param xmlw + * The <code>XMLStreamWriter</code> to write the entity to. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * + * @throws XMLStreamException + * if an error occurs creating or accessing the stream. + * @throws StorageException + * if a Storage service error occurs. + */ + protected static void writeSingleEntityToStream(final TableEntity entity, final boolean isTableEntry, + final XMLStreamWriter xmlw, final OperationContext opContext) throws XMLStreamException, StorageException { + // default is UTF8 + xmlw.writeStartDocument("UTF-8", "1.0"); + + writeEntityToStream(entity, isTableEntry, xmlw, opContext); + + // end doc + xmlw.writeEndDocument(); + xmlw.flush(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java new file mode 100644 index 0000000000000..b471594261c68 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java @@ -0,0 +1,1359 @@ +/** + * Copyright 2011 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.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; + +import javax.xml.stream.XMLStreamException; + +import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ResultContinuation; +import com.microsoft.windowsazure.services.core.storage.ResultContinuationType; +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.ServiceClient; +import com.microsoft.windowsazure.services.core.storage.StorageCredentials; +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.LazySegmentedIterator; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; + +/** + * Provides a service client for accessing the Windows Azure Table service. + * <p> + * The {@link CloudTableClient} class encapsulates the base URI for the Table service endpoint and the credentials for + * accessing the storage account, and provides methods to create, delete, list, and query tables, as well as methods to + * execute operations and queries on table entities. These methods invoke Storage Service REST API operations to make + * the requests and obtain the results that are returned. + * <p> + * A Table service endpoint is the base URI for Table service resources, including the DNS name of the storage account: + * <br> + * <code>    http://<em>myaccount</em>.table.core.windows.net</code><br> + * For more information, see the MSDN topic <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179360.aspx">Addressing Table Service Resources</a>. + * <p> + * The credentials can be a combination of the storage account name and a key, or a shared access signature. For more + * information, see the MSDN topic <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/hh225339.aspx">Authenticating Access to Your Storage + * Account</a>. + * + */ +public final class CloudTableClient extends ServiceClient { + + /** + * Reserved for internal use. An {@link EntityResolver} that projects table entity data as a <code>String</code> + * containing the table name. + */ + private final EntityResolver<String> tableNameResolver = new EntityResolver<String>() { + @Override + public String resolve(String partitionKey, String rowKey, Date timeStamp, + HashMap<String, EntityProperty> properties, String etag) { + return properties.get(TableConstants.TABLE_NAME).getValueAsString(); + } + }; + + /** + * Initializes an instance of the {@link CloudTableClient} class using a Table service endpoint. + * <p> + * A {@link CloudTableClient} initialized with this constructor must have storage account credentials added before + * it can be used to access the Windows Azure storage service. + * + * @param baseUri + * A <code>java.net.URI</code> that represents the Table service endpoint used to initialize the + * client. + */ + public CloudTableClient(final URI baseUri) { + this(baseUri, null); + this.setTimeoutInMs(TableConstants.TABLE_DEFAULT_TIMEOUT_IN_MS); + } + + /** + * Initializes an instance of the {@link CloudTableClient} class using a Table service endpoint and + * storage account credentials. + * + * @param baseUri + * A <code>java.net.URI</code> object that represents the Table service endpoint used to initialize the + * client. + * @param credentials + * A {@link StorageCredentials} object that represents the storage account credentials for access. + */ + public CloudTableClient(final URI baseUri, StorageCredentials credentials) { + super(baseUri, credentials); + this.setTimeoutInMs(TableConstants.TABLE_DEFAULT_TIMEOUT_IN_MS); + } + + /** + * Creates a table with the specified name in the storage service. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create + * Table</a> REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * @param tableName + * A <code>String</code> object containing the name of the table to create. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table cannot be + * created, or already exists. + */ + @DoesServiceRequest + public void createTable(final String tableName) throws StorageException { + this.createTable(tableName, null, null); + } + + /** + * Creates a table with the specified name in the storage service, using the specified {@link TableRequestOptions} + * and {@link OperationContext}. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create + * Table</a> REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> object containing the name of the table to create. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table cannot be + * created, or already exists. + */ + @DoesServiceRequest + public void createTable(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(this); + + Utility.assertNotNullOrEmpty("tableName", tableName); + + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); + + this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, TableOperation.insert(tableEntry), options, opContext); + } + + /** + * Creates a table with the specified name in the storage service, if it does not already exist. + * <p> + * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, and if not, invokes the <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create Table</a> Storage Service REST + * API to create the specified table, using the Table service endpoint and storage account credentials of this + * instance. + * + * @param tableName + * A <code>String</code> object containing the name of the table to create. + * + * @return + * A value of <code>true</code> if the operation created a new table, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table does not + * exist and cannot be created. + */ + @DoesServiceRequest + public boolean createTableIfNotExists(final String tableName) throws StorageException { + return this.createTableIfNotExists(tableName, null, null); + } + + /** + * Creates a table with the specified name in the storage service if it does not already exist, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, and if not, invokes the <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create Table</a> Storage Service REST + * API to create the specified table, using the Table service endpoint and storage account credentials of this + * instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> object containing the name of the table to create. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A value of <code>true</code> if the operation created a new table, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table does not + * exist and cannot be created. + */ + @DoesServiceRequest + public boolean createTableIfNotExists(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(this); + + Utility.assertNotNullOrEmpty("tableName", tableName); + + if (this.doesTableExist(tableName, options, opContext)) { + return false; + } + else { + try { + this.createTable(tableName, options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT + && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + } + + /** + * Deletes the table with the specified name in the storage service. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete + * Table</a> REST API to delete the specified table and any data it contains, using the Table service endpoint and + * storage account credentials of this instance. + * + * @param tableName + * A <code>String</code> object containing the name of the table to delete. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table deletion operation failed. + */ + @DoesServiceRequest + public void deleteTable(final String tableName) throws StorageException { + this.deleteTable(tableName, null, null); + } + + /** + * Deletes the table with the specified name in the storage service, using the specified {@link TableRequestOptions} + * and {@link OperationContext}. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete + * Table</a> REST API to delete the specified table and any data it contains, using the Table service endpoint and + * storage account credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> object containing the name of the table to delete. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table deletion operation failed. + */ + @DoesServiceRequest + public void deleteTable(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(this); + + Utility.assertNotNullOrEmpty("tableName", tableName); + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); + + final TableOperation delOp = new TableOperation(tableEntry, TableOperationType.DELETE); + + final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, delOp, options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { + return; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + } + + /** + * Deletes the table with the specified name in the storage service, if it exists. + * <p> + * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, and if so, invokes the <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete Table</a> Storage Service REST + * API to delete the table and any data it contains, using the Table service endpoint and storage account + * credentials of this instance. + * + * @param tableName + * A <code>String</code> object containing the name of the table to delete. + * + * @return + * A value of <code>true</code> if the operation deleted an existing table, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table deletion operation failed. + */ + @DoesServiceRequest + public boolean deleteTableIfExists(final String tableName) throws StorageException { + return this.deleteTableIfExists(tableName, null, null); + } + + /** + * Deletes the table with the specified name in the storage service, if it exists, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, and if so, invokes the <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete Table</a> Storage Service REST + * API to delete the table and any data it contains, using the Table service endpoint and storage account + * credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> object containing the name of the table to delete. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A value of <code>true</code> if the operation deleted an existing table, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table deletion operation failed. + */ + @DoesServiceRequest + public boolean deleteTableIfExists(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(this); + + Utility.assertNotNullOrEmpty("tableName", tableName); + + if (this.doesTableExist(tableName, options, opContext)) { + try { + this.deleteTable(tableName, options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND + && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + else { + return false; + } + } + + /** + * Determines if a table with the specified name exists in the storage service. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, using the Table service endpoint and storage account + * credentials of this instance. + * + * @param tableName + * A <code>String</code> object containing the name of the table to find. + * + * @return + * A value of <code>true</code> if the table exists, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service. + */ + @DoesServiceRequest + public boolean doesTableExist(final String tableName) throws StorageException { + return this.doesTableExist(tableName, null, null); + } + + /** + * Determines if a table with the specified name exists in the storage service, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to determine if the table exists, using the Table service endpoint and storage account + * credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> object containing the name of the table to find. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A value of <code>true</code> if the table exists, otherwise <code>false</code>. + * + * @throws StorageException + * if an error occurs accessing the storage service. + */ + @DoesServiceRequest + public boolean doesTableExist(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(this); + + Utility.assertNotNullOrEmpty("tableName", tableName); + + final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, + TableOperation.retrieve(tableName /* Used As PK */, null/* Row Key */, DynamicTableEntity.class), + options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) { + return true; + } + else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + } + + /** + * Executes the specified batch operation on a table as an atomic operation. A batch operation may contain up to 100 + * individual table operations, with the requirement that each operation entity must have same partition key. Only + * one retrieve operation is allowed per batch. Note that the total payload of a batch operation is limited to 4MB. + * <p> + * This method invokes an <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Entity Group + * Transaction</a> on the REST API to execute the specified batch operation on the table as an atomic unit, using + * the Table service endpoint and storage account credentials of this instance. + * + * @param tableName + * A <code>String</code> containing the name of the table to execute the operations on. + * @param batch + * The {@link TableBatchOperation} object representing the operations to execute on the table. + * + * @return + * A <code>java.util.ArrayList</code> of {@link TableResult} that contains the results, in order, of + * each {@link TableOperation} in the {@link TableBatchOperation} on the named table. + * + * @throws StorageException + * if an error occurs accessing the storage service, or the operation fails. + */ + @DoesServiceRequest + public ArrayList<TableResult> execute(final String tableName, final TableBatchOperation batch) + throws StorageException { + return this.execute(tableName, batch, null, null); + } + + /** + * Executes the specified batch operation on a table as an atomic operation, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. A batch operation may contain up to 100 individual + * table operations, with the requirement that each operation entity must have same partition key. Only one retrieve + * operation is allowed per batch. Note that the total payload of a batch operation is limited to 4MB. + * <p> + * This method invokes an <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Entity Group + * Transaction</a> on the REST API to execute the specified batch operation on the table as an atomic unit, using + * the Table service endpoint and storage account credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> containing the name of the table to execute the operations on. + * @param batch + * The {@link TableBatchOperation} object representing the operations to execute on the table. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A <code>java.util.ArrayList</code> of {@link TableResult} that contains the results, in order, of + * each {@link TableOperation} in the {@link TableBatchOperation} on the named table. + * + * @throws StorageException + * if an error occurs accessing the storage service, or the operation fails. + */ + @DoesServiceRequest + public ArrayList<TableResult> execute(final String tableName, final TableBatchOperation batch, + TableRequestOptions options, OperationContext opContext) throws StorageException { + Utility.assertNotNull("batch", batch); + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this); + return batch.execute(this, tableName, options, opContext); + } + + /** + * Executes the operation on a table. + * <p> + * This method will invoke the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to execute the specified operation on the table, using the Table service endpoint and + * storage account credentials of this instance. + * + * @param tableName + * A <code>String</code> containing the name of the table to execute the operation on. + * @param operation + * The {@link TableOperation} object representing the operation to execute on the table. + * + * @return + * A {@link TableResult} containing the result of executing the {@link TableOperation} on the table. + * + * @throws StorageException + * if an error occurs accessing the storage service, or the operation fails. + */ + @DoesServiceRequest + public TableResult execute(final String tableName, final TableOperation operation) throws StorageException { + return this.execute(tableName, operation, null, null); + } + + /** + * Executes the operation on a 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/dd179423.aspx">Table + * Service REST API</a> to execute the specified operation on the table, using the Table service endpoint and + * storage account credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param tableName + * A <code>String</code> containing the name of the table to execute the operation on. + * @param operation + * The {@link TableOperation} object representing the operation to execute on the table. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A {@link TableResult} containing the result of executing the {@link TableOperation} on the table. + * + * @throws StorageException + * if an error occurs accessing the storage service, or the operation fails. + */ + @DoesServiceRequest + public TableResult execute(final String tableName, final TableOperation operation, + final TableRequestOptions options, final OperationContext opContext) throws StorageException { + Utility.assertNotNull("operation", operation); + return operation.execute(this, tableName, options, opContext); + } + + /** + * Executes a query, applying the specified {@link EntityResolver} to the result. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. + * + * @return + * A collection implementing the <code>Iterable</code> interface containing the projection into type + * <code>R</code> of the results of executing the query. + */ + @DoesServiceRequest + public <R> Iterable<R> execute(final TableQuery<?> query, final EntityResolver<R> resolver) { + return this.execute(query, resolver, null, null); + } + + /** + * Executes a query, applying the specified {@link EntityResolver} to the result, using the + * specified {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A collection implementing the <code>Iterable</code> interface containing the projection into type + * <code>R</code> of the results of executing the query. + */ + @DoesServiceRequest + @SuppressWarnings("unchecked") + public <R> Iterable<R> execute(final TableQuery<?> query, final EntityResolver<R> resolver, + final TableRequestOptions options, final OperationContext opContext) { + Utility.assertNotNull("query", query); + Utility.assertNotNull("Query requires a valid class type or resolver.", resolver); + return (Iterable<R>) this.generateIteratorForQuery(query, resolver, options, opContext); + } + + /** + * Executes a query. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use, + * specialized for a type T implementing {@link TableEntity}. + * + * @return + * A collection implementing the <code>Iterable</code> interface specialized for type T of the results of + * executing the query. + */ + @DoesServiceRequest + public <T extends TableEntity> Iterable<T> execute(final TableQuery<T> query) { + return this.execute(query, null, null); + } + + /** + * Executes a query, using the specified {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use, + * specialized for a type T implementing {@link TableEntity}. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A collection implementing the <code>Iterable</code> interface specialized for type T of the results of + * executing the query. + */ + @SuppressWarnings("unchecked") + @DoesServiceRequest + public <T extends TableEntity> Iterable<T> execute(final TableQuery<T> query, final TableRequestOptions options, + final OperationContext opContext) { + Utility.assertNotNull("query", query); + return (Iterable<T>) this.generateIteratorForQuery(query, null, options, opContext); + } + + /** + * Executes a query in segmented mode with the specified {@link ResultContinuation} continuation token, + * applying the {@link EntityResolver} to the result. + * Executing a query with <code>executeSegmented</code> allows the query to be resumed after returning partial + * results, using information returned by the server in the {@link ResultSegment} object. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. + * @param continuationToken + * A {@link ResultContinuation} object representing a continuation token from the server when the + * operation returns a partial result. Specify <code>null</code> on the initial call. Call the + * {@link ResultSegment#getContinuationToken()} method on the result to obtain the + * {@link ResultContinuation} object to use in the next call to resume the query. + * + * @return + * A {@link ResultSegment} containing the projection into type <code>R</code> of the results of executing + * the query. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the query is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + public <R> ResultSegment<R> executeSegmented(final TableQuery<?> query, final EntityResolver<R> resolver, + final ResultContinuation continuationToken) throws IOException, URISyntaxException, StorageException { + return this.executeSegmented(query, resolver, continuationToken, null, null); + } + + /** + * Executes a query in segmented mode with the specified {@link ResultContinuation} continuation token, + * using the specified {@link TableRequestOptions} and {@link OperationContext}, applying the {@link EntityResolver} + * to the result. + * Executing a query with <code>executeSegmented</code> allows the query to be resumed after returning partial + * results, using information returned by the server in the {@link ResultSegment} object. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. + * @param continuationToken + * A {@link ResultContinuation} object representing a continuation token from the server when the + * operation returns a partial result. Specify <code>null</code> on the initial call. Call the + * {@link ResultSegment#getContinuationToken()} method on the result to obtain the + * {@link ResultContinuation} object to use in the next call to resume the query. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A {@link ResultSegment} containing the projection into type <code>R</code> of the results of executing + * the query. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the query is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + @SuppressWarnings("unchecked") + public <R> ResultSegment<R> executeSegmented(final TableQuery<?> query, final EntityResolver<R> resolver, + final ResultContinuation continuationToken, final TableRequestOptions options, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + Utility.assertNotNull("Query requires a valid class type or resolver.", resolver); + return (ResultSegment<R>) this + .executeQuerySegmentedImpl(query, resolver, continuationToken, options, opContext); + } + + /** + * Executes a query in segmented mode with a {@link ResultContinuation} continuation token. + * Executing a query with <code>executeSegmented</code> allows the query to be resumed after returning partial + * results, using information returned by the server in the {@link ResultSegment} object. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use, + * specialized for a type T implementing {@link TableEntity}. + * @param continuationToken + * A {@link ResultContinuation} object representing a continuation token from the server when the + * operation returns a partial result. Specify <code>null</code> on the initial call. Call the + * {@link ResultSegment#getContinuationToken()} method on the result to obtain the + * {@link ResultContinuation} object to use in the next call to resume the query. + * + * @return + * A {@link ResultSegment} specialized for type T of the results of executing the query. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the query is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + public <T extends TableEntity> ResultSegment<T> executeSegmented(final TableQuery<T> query, + final ResultContinuation continuationToken) throws IOException, URISyntaxException, StorageException { + return this.executeSegmented(query, continuationToken, null, null); + } + + /** + * Executes a query in segmented mode with a {@link ResultContinuation} continuation token, + * using the specified {@link TableRequestOptions} and {@link OperationContext}. + * Executing a query with <code>executeSegmented</code> allows the query to be resumed after returning partial + * results, using information returned by the server in the {@link ResultSegment} object. + * <p> + * This method will invoke a <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query + * Entities</a> operation on the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179423.aspx">Table + * Service REST API</a> to query the table, using the Table service endpoint and storage account credentials of this + * instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param query + * A {@link TableQuery} instance specifying the table to query and the query parameters to use, + * specialized for a type T implementing {@link TableEntity}. + * @param continuationToken + * A {@link ResultContinuation} object representing a continuation token from the server when the + * operation returns a partial result. Specify <code>null</code> on the initial call. Call the + * {@link ResultSegment#getContinuationToken()} method on the result to obtain the + * {@link ResultContinuation} object to use in the next call to resume the query. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A {@link ResultSegment} specialized for type T of the results of executing the query. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the query is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + @SuppressWarnings("unchecked") + public <T extends TableEntity> ResultSegment<T> executeSegmented(final TableQuery<T> query, + final ResultContinuation continuationToken, final TableRequestOptions options, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + Utility.assertNotNull("query", query); + return (ResultSegment<T>) this.executeQuerySegmentedImpl(query, null, continuationToken, options, opContext); + } + + /** + * Lists the table names in the storage account. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names, using the Table service endpoint and storage account credentials of + * this instance. + * + * @return + * An <code>Iterable</code> collection of the table names in the storage account. + */ + @DoesServiceRequest + public Iterable<String> listTables() { + return this.listTables(null); + } + + /** + * Lists the table names in the storage account that match the specified prefix. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names that match the prefix, using the Table service endpoint and storage + * account credentials of this instance. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * + * @return + * An <code>Iterable</code> collection of the table names in the storage account that match the specified + * prefix. + */ + @DoesServiceRequest + public Iterable<String> listTables(final String prefix) { + return this.listTables(prefix, null, null); + } + + /** + * Lists the table names in the storage account that match the specified prefix, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names that match the prefix, using the Table service endpoint and storage + * account credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>Iterable</code> collection of the table names in the storage account that match the specified + * prefix. + */ + @DoesServiceRequest + public Iterable<String> listTables(final String prefix, final TableRequestOptions options, + final OperationContext opContext) { + return this.execute(this.generateListTablesQuery(prefix), this.tableNameResolver, options, opContext); + } + + /** + * Lists the table names in the storage account in segmented mode. This method allows listing of tables to be + * resumed after returning a partial set of results, using information returned by the server in the + * {@link ResultSegment} object. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names, using the Table service endpoint and storage account credentials of + * this instance. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * + * @return + * A {@link ResultSegment} of <code>String</code> objects containing table names in the storage account. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the operation is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + public ResultSegment<String> listTablesSegmented() throws IOException, URISyntaxException, StorageException { + return this.listTablesSegmented(null); + } + + /** + * Lists the table names in the storage account that match the specified prefix in segmented mode. This method + * allows listing of tables to be resumed after returning a partial set of results, using information returned by + * the server in the {@link ResultSegment} object. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names that match the prefix, using the Table service endpoint and storage + * account credentials of this instance. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * + * @return + * A {@link ResultSegment} of <code>String</code> objects containing table names matching the prefix in the + * storage account. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the operation is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + public ResultSegment<String> listTablesSegmented(final String prefix) throws IOException, URISyntaxException, + StorageException { + return this.listTablesSegmented(prefix, null, null, null, null); + } + + /** + * Lists up to the specified maximum of the table names in the storage account that match the specified prefix in a + * resumable mode with the specified {@link ResultContinuation} continuation token, using the specified + * {@link TableRequestOptions} and {@link OperationContext}. This method allows listing of tables to be resumed + * after returning a page of results, using information returned by the server in the {@link ResultSegment} object. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query + * Tables</a> REST API to list the table names that match the prefix, using the Table service endpoint and storage + * account credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * @param maxResults + * The maximum number of table names to return in the {@link ResultSegment}. If this parameter is null, + * the query will list up to the maximum 1,000 results. + * @param continuationToken + * A {@link ResultContinuation} object representing a continuation token from the server when the + * operation returns a partial result. Specify <code>null</code> on the initial call. Call the + * {@link ResultSegment#getContinuationToken()} method on the result to obtain the + * {@link ResultContinuation} object to use in the next call to resume the query. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * A {@link ResultSegment} of <code>String</code> objects containing table names in the storage account. + * + * @throws IOException + * if an IO error occurred during the operation. + * @throws URISyntaxException + * if the URI generated for the operation is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + @DoesServiceRequest + public ResultSegment<String> listTablesSegmented(final String prefix, final Integer maxResults, + final ResultContinuation continuationToken, final TableRequestOptions options, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + return this.executeSegmented(this.generateListTablesQuery(prefix).take(maxResults), this.tableNameResolver, + continuationToken, options, opContext); + } + + /** + * Reserved for internal use. Generates a query to list table names with the given prefix. + * + * @param prefix + * A <String> containing the prefix to match on table names to return. + * @return + * A {@link TableQuery} instance for listing table names with the specified prefix. + */ + private TableQuery<TableServiceEntity> generateListTablesQuery(final String prefix) { + TableQuery<TableServiceEntity> listQuery = TableQuery.<TableServiceEntity> from( + TableConstants.TABLES_SERVICE_TABLES_NAME, TableServiceEntity.class); + + if (!Utility.isNullOrEmpty(prefix)) { + // Append Max char to end '{' is 1 + 'z' in AsciiTable > uppperBound = prefix + '{' + final String prefixFilter = String.format("(%s ge '%s') and (%s lt '%s{')", TableConstants.TABLE_NAME, + prefix, TableConstants.TABLE_NAME, prefix); + + listQuery = listQuery.where(prefixFilter); + } + + return listQuery; + } + + /** + * Reserved for internal use. Implements the REST API call at the core of a segmented table query + * operation. + * + * @param queryToExecute + * The {@link TableQuery} to execute. + * @param resolver + * An {@link EntityResolver} instance to use to project the result entity as an instance of type + * <code>R</code>. Pass <code>null</code> to return the results as the table entity type. + * @param continuationToken + * The {@link ResultContinuation} to pass with the operation to resume a query, if any. Pass + * <code>null</code> for an initial query. + * @param taskReference + * A reference to the {@link StorageOperation} implementing the segmented operation. + * @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 ResultSegment} containing a collection of the query results specialized for the + * {@link TableEntity} or {@link EntityResolver} type returned by the query. + * @throws StorageException + * if a Storage service error occurs. + * @throws IOException + * if an IO error occurs. + * @throws URISyntaxException + * if the URI generated for the query is invalid. + * @throws XMLStreamException + * if an error occurs accessing the <code>XMLStreamReader</code>. + * @throws ParseException + * if an error occurs in parsing the response. + * @throws InstantiationException + * if an error occurs in object construction. + * @throws IllegalAccessException + * if an error occurs in reflection on an object type. + * @throws InvalidKeyException + * if the key for an entity is invalid. + */ + @SuppressWarnings("unchecked") + protected <T extends TableEntity, R> ResultSegment<?> executeQuerySegmentedCore(final TableQuery<T> queryToExecute, + final EntityResolver<R> resolver, final ResultContinuation continuationToken, + final StorageOperation<?, ?, ?> taskReference, final TableRequestOptions options, + final OperationContext opContext) throws StorageException, IOException, URISyntaxException, + XMLStreamException, ParseException, InstantiationException, IllegalAccessException, InvalidKeyException { + if (resolver == null) { + Utility.assertNotNull("Query requires a valid class type or resolver.", queryToExecute.getClazzType()); + } + + final HttpURLConnection queryRequest = TableRequest.query(this.getEndpoint(), + queryToExecute.getSourceTableName(), null/* identity */, options.getTimeoutIntervalInMs(), + queryToExecute.generateQueryBuilder(), continuationToken, options, opContext); + + this.getCredentials().signRequestLite(queryRequest, -1L, opContext); + + taskReference.setResult(ExecutionEngine.processRequest(queryRequest, opContext)); + + if (taskReference.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + throw TableServiceException.generateTableServiceException(true, taskReference.getResult(), null, + queryRequest.getErrorStream()); + } + + ODataPayload<T> clazzResponse = null; + ODataPayload<R> resolvedResponse = null; + + InputStream inStream = queryRequest.getInputStream(); + + try { + if (resolver == null) { + clazzResponse = (ODataPayload<T>) AtomPubParser.parseResponse(inStream, queryToExecute.getClazzType(), + null, opContext); + } + else { + resolvedResponse = (ODataPayload<R>) AtomPubParser.parseResponse(inStream, + queryToExecute.getClazzType(), resolver, opContext); + } + } + finally { + inStream.close(); + } + + final ResultContinuation nextToken = TableResponse.getTableContinuationFromResponse(queryRequest); + + if (resolver == null) { + return new ResultSegment<T>(clazzResponse.results, + queryToExecute.getTakeCount() == null ? clazzResponse.results.size() + : queryToExecute.getTakeCount(), nextToken); + } + else { + return new ResultSegment<R>(resolvedResponse.results, + queryToExecute.getTakeCount() == null ? resolvedResponse.results.size() + : queryToExecute.getTakeCount(), nextToken); + } + } + + /** + * Reserved for internal use. Executes a segmented query operation using the specified retry and timeout policies. + * + * @param queryToExecute + * The {@link TableQuery} to execute. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. Pass <code>null</code> to return the results as the table entity + * type. + * @param continuationToken + * The {@link ResultContinuation} to pass with the operation to resume a query, if any. Pass + * <code>null</code> for an initial query. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * A {@link ResultSegment} containing a collection of the query results specialized for the + * {@link TableEntity} or {@link EntityResolver} type returned by the query. + * @throws StorageException + * if a Storage service error occurs. + */ + protected <T extends TableEntity, R> ResultSegment<?> executeQuerySegmentedImpl(final TableQuery<T> queryToExecute, + final EntityResolver<R> resolver, final ResultContinuation continuationToken, TableRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this); + + Utility.assertContinuationType(continuationToken, ResultContinuationType.TABLE); + + final StorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<?>> impl = new StorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<?>>( + options) { + @Override + public ResultSegment<?> execute(final CloudTableClient client, final TableQuery<T> queryRef, + final OperationContext opContext) throws Exception { + + return CloudTableClient.this.executeQuerySegmentedCore(queryRef, resolver, continuationToken, this, + (TableRequestOptions) this.getRequestOptions(), opContext); + } + }; + return ExecutionEngine.executeWithRetry(this, queryToExecute, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Reserved for internal use. Generates an iterator for a segmented query operation. + * + * @param queryRef + * The {@link TableQuery} to execute. + * @param resolver + * An {@link EntityResolver} instance which creates a projection of the table query result entities into + * the specified type <code>R</code>. Pass <code>null</code> to return the results as the table entity + * type. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * An instance of <code>Iterable</code> specialized for the {@link TableEntity} or {@link EntityResolver} + * type returned by the query. + */ + protected <T extends TableEntity, R> Iterable<?> generateIteratorForQuery(final TableQuery<T> queryRef, + final EntityResolver<R> resolver, TableRequestOptions options, OperationContext opContext) { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this); + + if (resolver == null) { + final SegmentedStorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<T>> impl = new SegmentedStorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<T>>( + options, null) { + @Override + public ResultSegment<T> execute(final CloudTableClient client, final TableQuery<T> queryToExecute, + final OperationContext opContext) throws Exception { + + @SuppressWarnings("unchecked") + final ResultSegment<T> result = (ResultSegment<T>) CloudTableClient.this.executeQuerySegmentedCore( + queryToExecute, null, this.getToken(), this, + (TableRequestOptions) this.getRequestOptions(), opContext); + + // Note, setting the token on the SegmentedStorageOperation is + // key, this is how the iterator will share the token across executions + if (result != null) { + this.setToken(result.getContinuationToken()); + } + + return result; + } + }; + + return new LazySegmentedIterator<CloudTableClient, TableQuery<T>, T>(impl, this, queryRef, + options.getRetryPolicyFactory(), opContext); + } + else { + final SegmentedStorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<R>> impl = new SegmentedStorageOperation<CloudTableClient, TableQuery<T>, ResultSegment<R>>( + options, null) { + @Override + public ResultSegment<R> execute(final CloudTableClient client, final TableQuery<T> queryToExecute, + final OperationContext opContext) throws Exception { + + @SuppressWarnings("unchecked") + final ResultSegment<R> result = (ResultSegment<R>) CloudTableClient.this.executeQuerySegmentedCore( + queryToExecute, resolver, this.getToken(), this, + (TableRequestOptions) this.getRequestOptions(), opContext); + + // Note, setting the token on the SegmentedStorageOperation is + // key, this is how the iterator will share the token across executions + if (result != null) { + this.setToken(result.getContinuationToken()); + } + + return result; + } + }; + return new LazySegmentedIterator<CloudTableClient, TableQuery<T>, R>(impl, this, queryRef, + options.getRetryPolicyFactory(), opContext); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/DynamicTableEntity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/DynamicTableEntity.java new file mode 100644 index 0000000000000..cfe58e086f67e --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/DynamicTableEntity.java @@ -0,0 +1,104 @@ +/** + * Copyright 2011 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.util.HashMap; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * A {@link TableEntity} type which allows callers direct access to the property map of the entity. This class extends + * {@link TableServiceEntity} to eliminate the use of reflection for serialization and deserialization. + * + */ +public class DynamicTableEntity extends TableServiceEntity { + private HashMap<String, EntityProperty> properties = new HashMap<String, EntityProperty>(); + + /** + * Nullary default constructor. + */ + public DynamicTableEntity() { + // empty ctor + } + + /** + * Constructs a {@link DynamicTableEntity} instance using the specified property map. + * + * @param properties + * A <code>java.util.HashMap</code> containing a map of <code>String</code> property names to + * {@link EntityProperty} data typed values to store in the new {@link DynamicTableEntity}. + */ + public DynamicTableEntity(final HashMap<String, EntityProperty> properties) { + this.setProperties(properties); + } + + /** + * Gets the property map for this {@link DynamicTableEntity} instance. + * + * @return + * A <code>java.util.HashMap</code> containing the map of <code>String</code> property names to + * {@link EntityProperty} data typed values for this {@link DynamicTableEntity} instance. + */ + public HashMap<String, EntityProperty> getProperties() { + return this.properties; + } + + /** + * Populates this {@link DynamicTableEntity} instance using the specified map of property names to + * {@link EntityProperty} data typed values. + * + * @param properties + * The <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} + * data + * typed values to store in this {@link DynamicTableEntity} instance. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + */ + @Override + public void readEntity(final HashMap<String, EntityProperty> properties, final OperationContext opContext) { + this.setProperties(properties); + } + + /** + * Sets the property map for this {@link DynamicTableEntity} instance. + * + * @param properties + * A <code>java.util.HashMap</code> containing the map of <code>String</code> property names to + * {@link EntityProperty} data typed values to set in this {@link DynamicTableEntity} instance. + */ + public void setProperties(final HashMap<String, EntityProperty> properties) { + this.properties = properties; + } + + /** + * Returns the map of property names to {@link EntityProperty} data values from this {@link DynamicTableEntity} + * instance. + * + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * + * @return + * A <code>java.util.HashMap</code> containing the map of <code>String</code> property names to + * {@link EntityProperty} data typed values stored in this {@link DynamicTableEntity} instance. + * @throws StorageException + * if a Storage service error occurs. + */ + @Override + public HashMap<String, EntityProperty> writeEntity(final OperationContext opContext) throws StorageException { + return this.getProperties(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EdmType.java new file mode 100644 index 0000000000000..bf246941e4ce5 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EdmType.java @@ -0,0 +1,203 @@ +/** + * Copyright 2011 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 com.microsoft.windowsazure.services.core.storage.Constants; + +/** + * A enumeration used to represent the primitive types of the Entity Data Model (EDM) in the Open Data Protocol (OData). + * The EDM is the underlying abstract data model used by OData services. The {@link EdmType} enumeration includes a + * {@link #parse(String)} method to convert EDM data type names to the enumeration type, and overrides the + * {@link #toString()} method to produce an EDM data type name. + * <p> + * For more information about OData, see the <a href="http://www.odata.org/">Open Data Protocol</a> website. + * <p> + * For an overview of the available EDM primitive data types and names, see the <a + * href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of the + * <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>. + * <p> + * The Abstract Type System used to define the primitive types supported by OData is defined in detail in <a + * href="http://msdn.microsoft.com/en-us/library/dd541474.aspx">[MC-CSDL] (section 2.2.1). + */ +public enum EdmType { + /** + * <strong>Null</strong> Represents the absence of a value + */ + NULL, + + /** + * <strong>Edm.Binary</strong> Represents fixed- or variable-length binary data + */ + BINARY, + + /** + * <strong>Edm.Boolean</strong> Represents the mathematical concept of binary-valued logic + */ + BOOLEAN, + + /** + * <strong>Edm.Byte</strong> Represents a unsigned 8-bit integer value + */ + BYTE, + + /** + * <strong>Edm.DateTime</strong> Represents date and time with values ranging from 12:00:00 midnight, January 1, + * 1753 A.D. through 11:59:59 P.M, December 9999 A.D. + */ + DATE_TIME, + + /** + * <strong>Edm.Decimal</strong> Represents numeric values with fixed precision and scale. This type can describe a + * numeric value ranging from negative 10^255 + 1 to positive 10^255 -1 + */ + DECIMAL, + + /** + * <strong>Edm.Double</strong> Represents a floating point number with 15 digits precision that can represent values + * with approximate range of +/- 2.23e -308 through +/- 1.79e +308 + */ + DOUBLE, + + /** + * <strong>Edm.Single</strong> Represents a floating point number with 7 digits precision that can represent values + * with approximate range of +/- 1.18e -38 through +/- 3.40e +38 + */ + SINGLE, + + /** + * <strong>Edm.Guid</strong> Represents a 16-byte (128-bit) unique identifier value + */ + GUID, + + /** + * <strong>Edm.Int16</strong> Represents a signed 16-bit integer value + */ + INT16, + + /** + * <strong>Edm.Int32</strong> Represents a signed 32-bit integer value + */ + INT32, + + /** + * <strong>Edm.Int64</strong> Represents a signed 64-bit integer value + */ + INT64, + + /** + * <strong>Edm.SByte</strong> Represents a signed 8-bit integer value + */ + SBYTE, + + /** + * <strong>Edm.String</strong> Represents fixed- or variable-length character data + */ + STRING, + + /** + * <strong>Edm.Time</strong> Represents the time of day with values ranging from 0:00:00.x to 23:59:59.y, where x + * and y depend upon the precision + */ + TIME, + + /** + * <strong>Edm.DateTimeOffset</strong> Represents date and time as an Offset in minutes from GMT, with values + * ranging from 12:00:00 midnight, January 1, 1753 A.D. through 11:59:59 P.M, December 9999 A.D + */ + DATE_TIME_OFFSET; + + /** + * Parses an EDM data type name and return the matching {@link EdmType} enumeration value. A <code>null</code> or + * empty value parameter is matched as {@link EdmType#STRING}. Note that only the subset of EDM data types + * supported in Windows Azure Table storage is parsed, consisting of {@link #BINARY}, {@link #BOOLEAN}, + * {@link #BYTE} , {@link #DATE_TIME}, {@link #DOUBLE}, {@link #GUID}, {@link #INT32}, {@link #INT64}, and + * {@link #STRING}. Any other type will cause an {@link IllegalArgumentException} to be thrown. + * + * @param value + * A <code>String</code> containing the name of an EDM data type. + * @return + * The {@link EdmType} enumeration value matching the specified EDM data type. + * @throws IllegalArgumentException + * if an EDM data type not supported in Windows Azure Table storage is passed as an argument. + * + */ + public static EdmType parse(final String value) { + if (value == null || value.length() == 0) { + return EdmType.STRING; + } + else if (value.equals(ODataConstants.EDMTYPE_DATETIME)) { + return EdmType.DATE_TIME; + } + else if (value.equals(ODataConstants.EDMTYPE_INT32)) { + return EdmType.INT32; + } + else if (value.equals(ODataConstants.EDMTYPE_BOOLEAN)) { + return EdmType.BOOLEAN; + } + else if (value.equals(ODataConstants.EDMTYPE_DOUBLE)) { + return EdmType.DOUBLE; + } + else if (value.equals(ODataConstants.EDMTYPE_INT64)) { + return EdmType.INT64; + } + else if (value.equals(ODataConstants.EDMTYPE_GUID)) { + return EdmType.GUID; + } + else if (value.equals(ODataConstants.EDMTYPE_BINARY)) { + return EdmType.BINARY; + } + + throw new IllegalArgumentException("Invalid value for edmtype: ".concat(value)); + } + + /** + * Returns the name of the EDM data type corresponding to the enumeration value. + * + * @return + * A <code>String</code> containing the name of the EDM data type. + */ + @Override + public String toString() { + if (this == EdmType.BINARY) { + return ODataConstants.EDMTYPE_BINARY; + } + else if (this == EdmType.STRING) { + return Constants.EMPTY_STRING; + } + else if (this == EdmType.BOOLEAN) { + return ODataConstants.EDMTYPE_BOOLEAN; + } + else if (this == EdmType.DOUBLE) { + return ODataConstants.EDMTYPE_DOUBLE; + } + else if (this == EdmType.GUID) { + return ODataConstants.EDMTYPE_GUID; + } + else if (this == EdmType.INT32) { + return ODataConstants.EDMTYPE_INT32; + } + else if (this == EdmType.INT64) { + return ODataConstants.EDMTYPE_INT64; + } + else if (this == EdmType.DATE_TIME) { + return ODataConstants.EDMTYPE_DATETIME; + } + else { + // VNext : Update here if we add to supported edmtypes in the future. + return Constants.EMPTY_STRING; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityProperty.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityProperty.java new file mode 100644 index 0000000000000..b449c874abb87 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityProperty.java @@ -0,0 +1,496 @@ +/** + * Copyright 2011 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.text.ParseException; +import java.util.Date; +import java.util.UUID; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Base64; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * A class which represents a single typed property value in a table entity. An {@link EntityProperty} stores the data + * type as an {@link EdmType}. The value, which may be <code>null</code> for object types, but not for primitive types, + * is serialized and stored as a <code>String</code>. + * <p> + * {@link EntityProperty} provides overloaded constructors and overloads of the <code>setValue</code> method for + * supported value types. Each overloaded constructor or <code>setValue</code> method sets the {@link EdmType} and + * serializes the value appropriately based on the parameter type. + * <p> + * Use one of the <code>getValueAs</code><em>Type</em> methods to deserialize an {@link EntityProperty} as the + * appropriate Java type. The method will throw a {@link ParseException} or {@link IllegalArgumentException} if the + * {@link EntityProperty} cannot be deserialized as the Java type. + */ +public final class EntityProperty { + private String value; + private EdmType edmType = EdmType.NULL; + private boolean isNull = false; + + /** + * Constructs an {@link EntityProperty} instance from a <code>boolean</code> value. + * + * @param value + * The <code>boolean</code> value of the entity property to set. + */ + public EntityProperty(final boolean value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>byte[]</code> value. + * + * @param value + * The <code>byte[]</code> value of the entity property to set. + */ + public EntityProperty(final byte[] value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>Byte[]</code>. + * + * @param value + * The <code>Byte[]</code> to set as the entity property value. + */ + public EntityProperty(final Byte[] value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>Date</code> value. + * + * @param value + * The <code>Date</code> to set as the entity property value. + */ + public EntityProperty(final Date value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>double</code> value. + * + * @param value + * The <code>double</code> value of the entity property to set. + */ + public EntityProperty(final double value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from an <code>int</code> value. + * + * @param value + * The <code>int</code> value of the entity property to set. + */ + public EntityProperty(final int value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>long</code> value. + * + * @param value + * The <code>long</code> value of the entity property to set. + */ + public EntityProperty(final long value) { + this.setValue(value); + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>String</code> value. + * + * @param value + * The <code>String</code> to set as the entity property value. + */ + public EntityProperty(final String value) { + this.setValue(value); + } + + /** + * Reserved for internal use. Constructs an {@link EntityProperty} instance from a <code>String</code> value and a + * data type, and verifies that the value can be interpreted as the specified data type. + * + * @param value + * The <code>String</code> representation of the value to construct. + * @param edmType + * The {@link EdmType} data type of the value to construct. + * @throws ParseException + * if the <code>String</code> representation of the value cannot be interpreted as the data type. + */ + protected EntityProperty(final String value, final EdmType edmType) throws ParseException { + this.edmType = edmType; + this.value = value; + + // validate data is encoded correctly + if (edmType == EdmType.STRING) { + return; + } + else if (edmType == EdmType.BINARY) { + this.getValueAsByteArray(); + } + else if (edmType == EdmType.BOOLEAN) { + this.getValueAsBoolean(); + } + else if (edmType == EdmType.DOUBLE) { + this.getValueAsDouble(); + } + else if (edmType == EdmType.GUID) { + this.getValueAsUUID(); + } + else if (edmType == EdmType.INT32) { + this.getValueAsInteger(); + } + else if (edmType == EdmType.INT64) { + this.getValueAsLong(); + } + else if (edmType == EdmType.DATE_TIME) { + this.getValueAsDate(); + } + } + + /** + * Constructs an {@link EntityProperty} instance from a <code>java.util.UUID</code> value. + * + * @param value + * The <code>java.util.UUID</code> to set as the entity property value. + */ + public EntityProperty(final UUID value) { + this.setValue(value); + } + + /** + * Reserved for internal use. Constructs an {@link EntityProperty} instance as a <code>null</code> value with the + * specified type. + * + * @param type + * The {@link EdmType} to set as the entity property type. + */ + protected EntityProperty(EdmType type) { + this.value = null; + this.edmType = type; + this.isNull = true; + } + + /** + * Gets the {@link EdmType} storage data type for the {@link EntityProperty}. + * + * @return + * The {@link EdmType} enumeration value for the data type of the {@link EntityProperty}. + */ + public EdmType getEdmType() { + return this.edmType; + } + + /** + * Gets a flag indicating that the {@link EntityProperty} value is <code>null</code>. + * + * @return + * A <code>boolean</code> flag indicating that the {@link EntityProperty} value is <code>null</code>. + */ + public boolean getIsNull() { + return this.isNull; + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>boolean</code>. + * + * @return + * A <code>boolean</code> representation of the {@link EntityProperty} value. + * + * @throws IllegalArgumentException + * if the value is <code>null</code> or cannot be parsed as a <code>boolean</code>. + */ + public boolean getValueAsBoolean() { + if (this.isNull) { + throw new IllegalArgumentException("EntityProperty cannot be set to null for value types."); + } + return Boolean.parseBoolean(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>byte</code> array. + * + * @return + * A <code>byte[]</code> representation of the {@link EntityProperty} value, or <code>null</code>. + */ + public byte[] getValueAsByteArray() { + return this.isNull ? null : Base64.decode(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>Byte</code> array. + * + * @return + * A <code>Byte[]</code> representation of the {@link EntityProperty} value, or <code>null</code>. + */ + public Byte[] getValueAsByteObjectArray() { + return this.isNull ? null : Base64.decodeAsByteObjectArray(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>Date</code>. + * + * @return + * A <code>Date</code> representation of the {@link EntityProperty} value, or <code>null</code>. + * + * @throws IllegalArgumentException + * if the value is not <code>null</code> and cannot be parsed as a <code>Date</code>. + */ + public Date getValueAsDate() { + if (this.isNull) { + return null; + } + + return Utility.parseDate(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>double</code>. + * + * @return + * A <code>double</code> representation of the {@link EntityProperty} value. + * + * @throws IllegalArgumentException + * if the value is <code>null</code> or cannot be parsed as a <code>double</code>. + */ + public double getValueAsDouble() { + if (this.isNull) { + throw new IllegalArgumentException("EntityProperty cannot be set to null for value types."); + } + return Double.parseDouble(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as an <code>int</code>. + * + * @return + * An <code>int</code> representation of the {@link EntityProperty} value. + * + * @throws IllegalArgumentException + * if the value is <code>null</code> or cannot be parsed as an <code>int</code>. + */ + public int getValueAsInteger() { + if (this.isNull) { + throw new IllegalArgumentException("EntityProperty cannot be set to null for value types."); + } + return Integer.parseInt(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>long</code>. + * + * @return + * A <code>long</code> representation of the {@link EntityProperty} value. + * + * @throws IllegalArgumentException + * if the value is <code>null</code> or cannot be parsed as a <code>long</code>. + */ + public long getValueAsLong() { + if (this.isNull) { + throw new IllegalArgumentException("EntityProperty cannot be set to null for value types."); + } + return Long.parseLong(this.value); + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>String</code>. + * + * @return + * A <code>String</code> representation of the {@link EntityProperty} value, or <code>null</code>. + */ + public String getValueAsString() { + return this.isNull ? null : this.value; + } + + /** + * Gets the value of this {@link EntityProperty} as a <code>java.util.UUID</code>. + * + * @return + * A <code>java.util.UUID</code> representation of the {@link EntityProperty} value, or <code>null</code>. + * + * @throws IllegalArgumentException + * if the value cannot be parsed as a <code>java.util.UUID</code>. + */ + public UUID getValueAsUUID() { + return this.isNull ? null : UUID.fromString(this.value); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>boolean</code> value. + * + * @param value + * The <code>boolean</code> value to set as the {@link EntityProperty} value. + */ + public synchronized final void setValue(final boolean value) { + this.edmType = EdmType.BOOLEAN; + this.isNull = false; + this.value = value ? Constants.TRUE : Constants.FALSE; + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>byte[]</code> value. + * + * @param value + * The <code>byte[]</code> value to set as the {@link EntityProperty} value. This value may be + * <code>null</code>. + */ + public synchronized final void setValue(final byte[] value) { + this.edmType = EdmType.BINARY; + if (value == null) { + this.value = null; + this.isNull = true; + return; + } + else { + this.isNull = false; + } + + this.value = Base64.encode(value); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>Byte[]</code> value. + * + * @param value + * The <code>Byte[]</code> value to set as the {@link EntityProperty} value. This value may be + * <code>null</code>. + */ + public synchronized final void setValue(final Byte[] value) { + this.edmType = EdmType.BINARY; + if (value == null) { + this.value = null; + this.isNull = true; + return; + } + else { + this.isNull = false; + } + + this.value = Base64.encode(value); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>Date</code> value. + * + * @param value + * The <code>Date</code> value to set as the {@link EntityProperty} value. This value may be + * <code>null</code>. + */ + public synchronized final void setValue(final Date value) { + this.edmType = EdmType.DATE_TIME; + + if (value == null) { + this.value = null; + this.isNull = true; + return; + } + else { + this.isNull = false; + } + + this.value = Utility.getTimeByZoneAndFormat(value, Utility.UTC_ZONE, Utility.ISO8061_LONG_PATTERN); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>double</code> value. + * + * @param value + * The <code>double</code> value to set as the {@link EntityProperty} value. + */ + public synchronized final void setValue(final double value) { + this.edmType = EdmType.DOUBLE; + this.isNull = false; + this.value = Double.toString(value); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>int</code> value. + * + * @param value + * The <code>int</code> value to set as the {@link EntityProperty} value. + */ + public synchronized final void setValue(final int value) { + this.edmType = EdmType.INT32; + this.isNull = false; + this.value = Integer.toString(value); + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>long</code> value. + * + * @param value + * The <code>long</code> value to set as the {@link EntityProperty} value. + */ + public synchronized final void setValue(final long value) { + this.edmType = EdmType.INT64; + this.isNull = false; + this.value = Long.toString(value); + } + + /** + * Sets this {@link EntityProperty} using the <code>String</code> value. + * + * @param value + * The <code>String</code> value to set as the {@link EntityProperty} value. This value may be + * <code>null</code>. + */ + public synchronized final void setValue(final String value) { + this.edmType = EdmType.STRING; + if (value == null) { + this.value = null; + this.isNull = true; + return; + } + else { + this.isNull = false; + } + + this.value = value; + } + + /** + * Sets this {@link EntityProperty} using the serialized <code>java.util.UUID</code> value. + * + * @param value + * The <code>java.util.UUID</code> value to set as the {@link EntityProperty} value. + * This value may be <code>null</code>. + */ + public synchronized final void setValue(final UUID value) { + this.edmType = EdmType.GUID; + if (value == null) { + this.value = null; + this.isNull = true; + return; + } + else { + this.isNull = false; + } + + this.value = value.toString(); + } + + /** + * Reserved for internal use. Sets the null value flag to the specified <code>boolean</code> value. + * + * @param isNull + * The <code>boolean</code> value to set in the null value flag. + */ + protected void setIsNull(final boolean isNull) { + this.isNull = isNull; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityResolver.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityResolver.java new file mode 100644 index 0000000000000..1f7d95d3a812a --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/EntityResolver.java @@ -0,0 +1,61 @@ +/** + * Copyright 2011 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.util.Date; +import java.util.HashMap; + +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * An interface to perform client side projection on a retrieved entity. An {@link EntityResolver} instance must + * implement a <code>resolve</code> method projecting the entity data represented by the parameters passed in as a new + * instance of the type specified by the type parameter. + * <p> + * This interface is useful for converting directly from table entity data to a client object type without requiring a + * separate table entity class type that deserializes every property individually. For example, a client can perform a + * client side projection of a <em>Customer</em> entity by simply returning the <code>String</code> for the + * <em>CustomerName</em> property of each entity. The result of this projection will be a collection of + * <code>String</code>s containing each customer name. + * + * @param <T> + * The type of the object that the resolver produces. + */ +public interface EntityResolver<T> { + /** + * Returns a reference to a new object instance of type <code>T</code> containing a projection of the specified + * table entity data. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity. + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity. + * @param timeStamp + * A <code>Date</code> containing the Timestamp value for the entity. + * @param properties + * The <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} + * data type and value pairs representing the table entity data. + * @param etag + * A <code>String</code> containing the Etag for the entity. + * @return + * A reference to an object instance of type <code>T</code> constructed as a projection of the table entity + * parameters. + * @throws StorageException + * if an error occurs during the operation. + */ + T resolve(String partitionKey, String rowKey, Date timeStamp, HashMap<String, EntityProperty> properties, + String etag) throws StorageException; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/Ignore.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/Ignore.java new file mode 100644 index 0000000000000..53653dc951505 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/Ignore.java @@ -0,0 +1,37 @@ +/** + * Copyright 2011 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation set on a method to prevent its use in serializing or deserializing a property by reflection. Apply the + * <code>@Ignore</code> annotation to methods in a class implementing {@link TableEntity} to force them to be ignored + * during reflection-based serialization and deserialization. See the documentation for {@link TableServiceEntity} for + * more information on using reflection-based serialization and deserialization. + * + * @see StoreAs + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface Ignore { + // No attributes +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHeader.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHeader.java new file mode 100644 index 0000000000000..bffc95cdd4276 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHeader.java @@ -0,0 +1,25 @@ +/** + * Copyright 2011 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; + +/** + * Reserved for internal use. A class that represents a given MIME Header. + */ +class MimeHeader { + protected String boundary; + protected String contentType; + protected String contentTransferEncoding; + protected String subBoundary; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHelper.java new file mode 100644 index 0000000000000..143a4022e3740 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimeHelper.java @@ -0,0 +1,510 @@ +/** + * Copyright 2011 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.net.URISyntaxException; +import java.util.ArrayList; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +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; + +/** + * Reserved for internal use. A class used to read and write MIME requests and responses. + */ +class MimeHelper { + /** + * Reserved for internal use. A static factory method that generates a {@link StorageException} for invalid MIME + * responses. + * + * @return + * The {@link StorageException} for the invalid MIME response. + */ + protected static StorageException generateMimeParseException() { + return new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, "Invalid MIME response received.", + Constants.HeaderConstants.HTTP_UNUSED_306, null, null); + } + + /** + * Reserved for internal use. Returns the HTTP verb for a table operation. + * + * @param operation + * The {@link TableOperation} instance to get the HTTP verb for. + * @return + * A <code>String</code> containing the HTTP verb to use with the operation. + */ + protected static String getHttpVerbForOperation(final TableOperation operation) { + if (operation.getOperationType() == TableOperationType.INSERT) { + return "POST"; + } + else if (operation.getOperationType() == TableOperationType.DELETE) { + return "DELETE"; + } + else if (operation.getOperationType() == TableOperationType.MERGE + || operation.getOperationType() == TableOperationType.INSERT_OR_MERGE) { + return "MERGE"; + } + else if (operation.getOperationType() == TableOperationType.REPLACE + || operation.getOperationType() == TableOperationType.INSERT_OR_REPLACE) { + return "PUT"; + } + else if (operation.getOperationType() == TableOperationType.RETRIEVE) { + return "GET"; + } + else { + throw new IllegalArgumentException("Unknown table operation"); + } + } + + /** + * Reserved for internal use. Returns the next non-blank line from the {@link BufferedReader}. + * + * @param reader + * The {@link BufferedReader} to read lines from. + * @return + * A <code>String</code> containing the next non-blank line from the {@link BufferedReader}, or + * <code>null</code>. + * @throws IOException + * if an error occurs reading from the {@link BufferedReader}. + */ + protected static String getNextLineSkippingBlankLines(final BufferedReader reader) throws IOException { + String tString = null; + do { + tString = reader.readLine(); + } while (tString != null && tString.length() == 0); + + return tString; + } + + /** + * Reserved for internal use. Reads the response stream from a batch operation into an <code>ArrayList</code> of + * {@link MimePart} objects. + * + * @param inStream + * An {@link InputStream} containing the operation response stream. + * @param expectedBundaryName + * A <code>String</code> containing the MIME part boundary string. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * An <code>ArrayList</code> of {@link MimePart} objects parsed from the input stream. + * @throws IOException + * if an error occurs accessing the input stream. + * @throws StorageException + * if an error occurs parsing the input stream. + */ + protected static ArrayList<MimePart> readBatchResponseStream(final InputStream inStream, + final String expectedBundaryName, final OperationContext opContext) throws IOException, StorageException { + final ArrayList<MimePart> result = new ArrayList<MimePart>(); + final InputStreamReader streamReader = new InputStreamReader(inStream, "UTF-8"); + final BufferedReader reader = new BufferedReader(streamReader); + final String mungedExpectedBoundaryName = "--".concat(expectedBundaryName); + + final MimeHeader docHeader = readMimeHeader(reader, opContext); + if (docHeader.boundary == null || !docHeader.boundary.equals(mungedExpectedBoundaryName)) { + throw generateMimeParseException(); + } + + MimeHeader currHeader = null; + + // No explicit changeset present + if (docHeader.subBoundary == null) { + do { + result.add(readMimePart(reader, docHeader.boundary, opContext)); + currHeader = readMimeHeader(reader, opContext); + } while (currHeader != null); + } + else { + // explicit changeset present. + currHeader = readMimeHeader(reader, opContext); + if (currHeader == null) { + throw new TableServiceException( + -1, + "An Error Occurred while processing the request, check the extended error information for more details.", + null, reader); + } + else { + do { + result.add(readMimePart(reader, docHeader.subBoundary, opContext)); + currHeader = readMimeHeader(reader, opContext); + } while (currHeader != null); + } + } + + return result; + } + + /** + * Reserved for internal use. A static factory method that constructs a {@link MimeHeader} by parsing the MIME + * header + * data from a {@link BufferedReader}. + * + * @param reader + * The {@link BufferedReader} containing the response stream to parse. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * A {@link MimeHeader} constructed by parsing the MIME header data from the {@link BufferedReader}. + * @throws IOException + * if an error occurs accessing the input stream. + * @throws StorageException + * if an error occurs parsing the input stream. + */ + protected static MimeHeader readMimeHeader(final BufferedReader reader, final OperationContext opContext) + throws IOException, StorageException { + final MimeHeader retHeader = new MimeHeader(); + reader.mark(1024 * 1024); + + // First thing is separator + retHeader.boundary = getNextLineSkippingBlankLines(reader); + if (retHeader.boundary.endsWith("--")) { + return null; + } + if (!retHeader.boundary.startsWith("--")) { + reader.reset(); + return null; + } + + for (int m = 0; m < 2; m++) { + final String tempString = reader.readLine(); + if (tempString.length() == 0) { + break; + } + + if (tempString.startsWith("Content-Type:")) { + final String[] headerVals = tempString.split("Content-Type: "); + if (headerVals == null || headerVals.length != 2) { + throw generateMimeParseException(); + } + retHeader.contentType = headerVals[1]; + } + else if (tempString.startsWith("Content-Transfer-Encoding:")) { + final String[] headerVals = tempString.split("Content-Transfer-Encoding: "); + if (headerVals == null || headerVals.length != 2) { + throw generateMimeParseException(); + } + retHeader.contentTransferEncoding = headerVals[1]; + } + else { + throw generateMimeParseException(); + } + } + + // Validate headers + if (Utility.isNullOrEmpty(retHeader.boundary) || retHeader.contentType == null) { + throw generateMimeParseException(); + } + + if (retHeader.contentType.startsWith("multipart/mixed; boundary=")) { + final String[] headerVals = retHeader.contentType.split("multipart/mixed; boundary="); + if (headerVals == null || headerVals.length != 2) { + throw generateMimeParseException(); + } + retHeader.subBoundary = "--".concat(headerVals[1]); + } + else if (!retHeader.contentType.equals("application/http")) { + throw generateMimeParseException(); + } + + if (retHeader.contentTransferEncoding != null && !retHeader.contentTransferEncoding.equals("binary")) { + throw generateMimeParseException(); + } + + return retHeader; + } + + // Returns at start of next mime boundary header + /** + * Reserved for internal use. A static factory method that generates a {@link MimePart} containing the next MIME + * part read from the {@link BufferedReader}. + * The {@link BufferedReader} is left positioned at the start of the next MIME boundary header. + * + * @param reader + * The {@link BufferedReader} containing the response stream to parse. + * @param boundary + * A <code>String</code> containing the MIME part boundary string. + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * A {@link MimePart} constructed by parsing the next MIME part data from the {@link BufferedReader}. + * @throws IOException + * if an error occured accessing the input stream. + * @throws StorageException + * if an error occured parsing the input stream. + */ + protected static MimePart readMimePart(final BufferedReader reader, final String boundary, + final OperationContext opContext) throws IOException, StorageException { + final MimePart retPart = new MimePart(); + // Read HttpStatus code + String tempStr = getNextLineSkippingBlankLines(reader); + if (!tempStr.startsWith("HTTP/1.1 ")) { + throw generateMimeParseException(); + } + + final String[] headerVals = tempStr.split(" "); + + if (headerVals.length < 3) { + throw generateMimeParseException(); + } + + retPart.httpStatusCode = Integer.parseInt(headerVals[1]); + // "HTTP/1.1 XXX ".length() => 13 + retPart.httpStatusMessage = tempStr.substring(13); + + // Read headers + tempStr = reader.readLine(); + while (tempStr != null && tempStr.length() > 0) { + final String[] headerParts = tempStr.split(": "); + if (headerParts.length < 2) { + throw generateMimeParseException(); + } + + retPart.headers.put(headerParts[0], headerParts[1]); + tempStr = reader.readLine(); + } + + // Store xml payload + reader.mark(1024 * 1024); + tempStr = getNextLineSkippingBlankLines(reader); + + if (tempStr == null) { + throw generateMimeParseException(); + } + + // empty body + if (tempStr.startsWith(boundary)) { + reader.reset(); + retPart.payload = Constants.EMPTY_STRING; + return retPart; + } + else if (!tempStr.startsWith("<?xml version=")) { + throw generateMimeParseException(); + } + final StringBuilder payloadBuilder = new StringBuilder(); + // read until mime closure or end of file + while (!tempStr.startsWith(boundary)) { + payloadBuilder.append(tempStr); + reader.mark(1024 * 1024); + tempStr = getNextLineSkippingBlankLines(reader); + if (tempStr == null) { + throw generateMimeParseException(); + } + } + + // positions stream at start of next MIME Header + reader.reset(); + + retPart.payload = payloadBuilder.toString(); + + return retPart; + } + + /** + * Reserved for internal use. Writes the batch operation to the output stream using batch request syntax. + * Batch request syntax is described in the MSDN topic <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Performing Entity Group + * Transactions</a>. + * + * @param outStream + * The {@link OutputStream} to write the batch request to. + * @param tableName + * A <code>String</code> containing the name of the table to apply each operation to. + * @param batch + * A {@link TableBatchOperation} containing the operations to write to the output stream + * @param batchID + * A <code>String</code> containing the identifier to use as the MIME boundary for the batch request. + * @param changeSet + * A <code>String</code> containing the identifier to use as the MIME boundary for operations within the + * batch. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @throws IOException + * if an IO error occurs. + * @throws URISyntaxException + * if an invalid URI is used. + * @throws StorageException + * if an error occurs accessing the Storage service. + * @throws XMLStreamException + * if an error occurs accessing the stream. + */ + protected static void writeBatchToStream(final OutputStream outStream, final String tableName, + final TableBatchOperation batch, final String batchID, final String changeSet, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException, + XMLStreamException { + final OutputStreamWriter outWriter = new OutputStreamWriter(outStream, "UTF8"); + + int contentID = 0; + boolean inChangeSet = false; + for (final TableOperation op : batch) { + if (op.getOperationType() == TableOperationType.RETRIEVE) { + final QueryTableOperation qOp = (QueryTableOperation) op; + + if (inChangeSet) { + inChangeSet = false; + // Write Boundary end. + MimeHelper.writeMIMEBoundaryClosure(outWriter, changeSet); + outWriter.write("\r\n"); + } + + // Write MIME Header + MimeHelper.writeMIMEBoundary(outWriter, batchID); + outWriter.write("Content-Type: application/http\r\n"); + outWriter.write("Content-Transfer-Encoding: binary\r\n\r\n"); + + outWriter.write(String.format("%s %s HTTP/1.1\r\n", getHttpVerbForOperation(op), + qOp.generateRequestIdentityWithTable(tableName))); + + outWriter.write("Host: host\r\n\r\n"); + } + else { + if (!inChangeSet) { + inChangeSet = true; + // New batch mime part + MimeHelper.writeMIMEBoundary(outWriter, batchID); + MimeHelper.writeMIMEContentType(outWriter, changeSet); + outWriter.write("\r\n"); + } + + // New mime part for changeset + MimeHelper.writeMIMEBoundary(outWriter, changeSet); + + // Write Headers + outWriter.write("Content-Type: application/http\r\n"); + outWriter.write("Content-Transfer-Encoding: binary\r\n\r\n"); + + outWriter.write(String.format("%s %s HTTP/1.1\r\n", getHttpVerbForOperation(op), + op.generateRequestIdentityWithTable(tableName))); + + outWriter.write(String.format("Content-ID: %s\r\n", Integer.toString(contentID))); + + if (op.getOperationType() != TableOperationType.INSERT + && op.getOperationType() != TableOperationType.INSERT_OR_MERGE + && op.getOperationType() != TableOperationType.INSERT_OR_REPLACE) { + outWriter.write(String.format("If-Match: %s\r\n", op.getEntity().getEtag())); + } + + if (op.getOperationType() == TableOperationType.DELETE) { + // empty body + outWriter.write("\r\n"); + } + else { + outWriter.write("Content-Type: application/atom+xml;type=entry\r\n"); + final String opString = writeStringForOperation(op, opContext); + outWriter.write(String.format("Content-Length: %s\r\n\r\n", + Integer.toString(opString.getBytes("UTF-8").length))); + outWriter.write(opString); + } + contentID = contentID + 1; + } + } + + if (inChangeSet) { + MimeHelper.writeMIMEBoundaryClosure(outWriter, changeSet); + } + MimeHelper.writeMIMEBoundaryClosure(outWriter, batchID); + + outWriter.flush(); + } + + /** + * Reserved for internal use. Writes a MIME part boundary to the output stream. + * + * @param outWriter + * The {@link OutputStreamWriter} to write the MIME part boundary to. + * @param boundaryID + * The <code>String</code> containing the MIME part boundary string. + * @throws IOException + * if an error occurs writing to the output stream. + */ + protected static void writeMIMEBoundary(final OutputStreamWriter outWriter, final String boundaryID) + throws IOException { + outWriter.write(String.format("--%s\r\n", boundaryID)); + } + + /** + * Reserved for internal use. Writes a MIME part boundary closure to the output stream. + * + * @param outWriter + * The {@link OutputStreamWriter} to write the MIME part boundary closure to. + * @param boundaryID + * The <code>String</code> containing the MIME part boundary string. + * @throws IOException + * if an error occurs writing to the output stream. + */ + protected static void writeMIMEBoundaryClosure(final OutputStreamWriter outWriter, final String boundaryID) + throws IOException { + outWriter.write(String.format("--%s--\r\n", boundaryID)); + } + + /** + * Reserved for internal use. Writes a MIME content type string to the output stream. + * + * @param outWriter + * The {@link OutputStreamWriter} to write the MIME content type string to. + * @param boundaryID + * The <code>String</code> containing the MIME part boundary string. + * @throws IOException + * if an error occurs writing to the output stream. + */ + protected static void writeMIMEContentType(final OutputStreamWriter outWriter, final String boundaryName) + throws IOException { + outWriter.write(String.format("Content-Type: multipart/mixed; boundary=%s\r\n", boundaryName)); + } + + /** + * Reserved for internal use. Generates a <code>String</code> containing the entity associated with an operation in + * AtomPub format. + * + * @param operation + * A {@link TableOperation} containing the entity to write to the returned <code>String</code>. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * @return + * A <code>String</code> containing the entity associated with the operation in AtomPub format + * @throws StorageException + * if a Storage error occurs. + * @throws XMLStreamException + * if an error occurs creating or writing to the output string. + */ + protected static String writeStringForOperation(final TableOperation operation, final OperationContext opContext) + throws StorageException, XMLStreamException { + final StringWriter outWriter = new StringWriter(); + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); + + AtomPubParser.writeSingleEntityToStream(operation.getEntity(), false, xmlw, opContext); + outWriter.write("\r\n"); + + return outWriter.toString(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimePart.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimePart.java new file mode 100644 index 0000000000000..254581502e2b8 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/MimePart.java @@ -0,0 +1,28 @@ +/** + * Copyright 2011 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.util.HashMap; + +/** + * Reserved for internal use. A class that represents a given MIME Part. + */ +class MimePart { + protected int httpStatusCode = -1; + protected String httpStatusMessage; + protected HashMap<String, String> headers = new HashMap<String, String>(); + protected String payload; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataConstants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataConstants.java new file mode 100644 index 0000000000000..ed89049797d11 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataConstants.java @@ -0,0 +1,183 @@ +/** + * Copyright 2011 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; + +/** + * Reserved for internal use. A class that holds relevant constants for interacting with OData feeds. + */ +class ODataConstants { + /** + * The <code>String</code> representation of the Atom namespace. + */ + public static final String ATOM_NS = "http://www.w3.org/2005/Atom"; + + /** + * The <code>String</code> representation of the OData Data namespace. + */ + public static final String DATA_SERVICES_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices"; + + /** + * The <code>String</code> representation of the OData Metadata namespace. + */ + public static final String DATA_SERVICES_METADATA_NS = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"; + + /** + * The <code>String</code> representation of the Atom namespace in brackets. + */ + public static final String BRACKETED_ATOM_NS = "{" + ATOM_NS + "}"; // default + + /** + * The <code>String</code> representation of the OData Data namespace in brackets. + */ + public static final String BRACKETED_DATA_SERVICES_NS = "{" + DATA_SERVICES_NS + "}"; // d: + + /** + * The <code>String</code> representation of the OData Metadata namespace in brackets. + */ + public static final String BRACKETED_DATA_SERVICES_METADATA_NS = "{" + DATA_SERVICES_METADATA_NS + "}"; // m: + + /** + * The <code>String</code> representation of the Atom Entry <em>feed</em> element name. + */ + public static final String FEED = "feed"; + + /** + * The <code>String</code> representation of the Atom Entry <em>title</em> element name. + */ + public static final String TITLE = "title"; + + /** + * The <code>String</code> representation of the Atom Entry <em>id</em> element name. + */ + public static final String ID = "id"; + + /** + * The <code>String</code> representation of the Atom Entry <em>updated</em> element name. + */ + public static final String UPDATED = "updated"; + + /** + * The <code>String</code> representation of the Atom Entry <em>link</em> element name. + */ + public static final String LINK = "link"; + + /** + * The <code>String</code> representation of the Atom Entry <em>author</em> element name. + */ + public static final String AUTHOR = "author"; + + /** + * The <code>String</code> representation of the Atom Entry <em>name</em> element name. + */ + public static final String NAME = "name"; + + /** + * The <code>String</code> representation of the Atom Entry <em>entry</em> element name. + */ + public static final String ENTRY = "entry"; + + /** + * The <code>String</code> representation of the Atom Entry <em>category</em> element name. + */ + public static final String CATEGORY = "category"; + + /** + * The <code>String</code> representation of the Atom Entry <em>content</em> element name. + */ + public static final String CONTENT = "content"; + + /** + * The <code>String</code> representation of the OData Metadata <em>properties</em> element name. + */ + public static final String PROPERTIES = "properties"; + + /** + * The <code>String</code> representation of the Atom Entry <em>etag</em> element name. + */ + public static final String ETAG = "etag"; + + /** + * The <code>String</code> representation of the <em>type</em> attribute name. + */ + public static final String TYPE = "type"; + + /** + * The <code>String</code> representation of the <em>term</em> element name. + */ + public static final String TERM = "term"; + + /** + * The <code>String</code> representation of <em>scheme</em>. + */ + public static final String SCHEME = "scheme"; + + /** + * The <code>String</code> representation of <em>href</em>. + */ + public static final String HREF = "href"; + + /** + * The <code>String</code> representation of <em>rel</em>. + */ + public static final String REL = "rel"; + + /** + * The <code>String</code> representation of the <em>null</em> attribute name. + */ + public static final String NULL = "null"; + + /** + * The <code>String</code> representation of the content type attribute value to send. + */ + public static final String ODATA_CONTENT_TYPE = "application/xml"; + + // Odata edm types + + /** + * The <code>String</code> representation of the <em>Edm.DateTime</em> metadata type attribute value. + */ + public static final String EDMTYPE_DATETIME = "Edm.DateTime"; + + /** + * The <code>String</code> representation of the <em>Edm.Binary</em> metadata type attribute value. + */ + public static final String EDMTYPE_BINARY = "Edm.Binary"; + + /** + * The <code>String</code> representation of the <em>Edm.Boolean</em> metadata type attribute value. + */ + public static final String EDMTYPE_BOOLEAN = "Edm.Boolean"; + + /** + * The <code>String</code> representation of the <em>Edm.Double</em> metadata type attribute value. + */ + public static final String EDMTYPE_DOUBLE = "Edm.Double"; + + /** + * The <code>String</code> representation of the <em>Edm.Guid</em> metadata type attribute value. + */ + public static final String EDMTYPE_GUID = "Edm.Guid"; + + /** + * The <code>String</code> representation of the <em>Edm.Int32</em> metadata type attribute value. + */ + public static final String EDMTYPE_INT32 = "Edm.Int32"; + + /** + * The <code>String</code> representation of the <em>Edm.Int64</em> metadata type attribute value. + */ + public static final String EDMTYPE_INT64 = "Edm.Int64"; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataPayload.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataPayload.java new file mode 100644 index 0000000000000..9f0f5c573becb --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/ODataPayload.java @@ -0,0 +1,42 @@ +/** + * Copyright 2011 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.util.ArrayList; + +/** + * Reserved for internal use. A class that represents an OData payload and resulting entities. + */ +class ODataPayload<T> { + /** + * A collection of table entities. + */ + protected ArrayList<T> results; + + /** + * A collection of {@link TableResults} which include additional information about the entities returned by an + * operation. + */ + protected ArrayList<TableResult> tableResults; + + /** + * Constructs an {@link ODataPayload} instance with new empty entity and {@link TableResult} collections. + */ + protected ODataPayload() { + this.results = new ArrayList<T>(); + this.tableResults = new ArrayList<TableResult>(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/PropertyPair.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/PropertyPair.java new file mode 100644 index 0000000000000..24da5f2adc585 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/PropertyPair.java @@ -0,0 +1,277 @@ +/** + * Copyright 2011 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.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.UUID; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * Reserved for internal use. A class used internally during the reflection process to determine which properties should + * be serialized. + */ +class PropertyPair { + /** + * Reserved for internal use. A static factory method to generate a map of property names to {@link PropertyPair} + * instances for the specified class type. Uses reflection to find pairs of getter and setter methods that are + * annotated with {@link StoreAs} with a common property name, or of the form <code>get<em>PropertyName</em></code> + * and <code>set<em>PropertyName</em></code>, with a common type for the getter return value and the + * setter parameter, and stores the methods and the property name for each pair found in a map for use in + * serializing and deserializing entity data. + * + * @param clazzType + * The class type to check for matching getter and setter methods with a common return and parameter + * type, respectively. + */ + protected static HashMap<String, PropertyPair> generatePropertyPairs(final Class<?> clazzType) { + final Method[] methods = clazzType.getMethods(); + final HashMap<String, PropertyPair> propMap = new HashMap<String, PropertyPair>(); + + String propName = null; + PropertyPair currProperty = null; + + for (final Method m : methods) { + if (m.getName().length() < 4 || (!m.getName().startsWith("get") && !m.getName().startsWith("set"))) { + continue; + } + + // TODO add logging + // System.out.println(m.getName()); + + propName = m.getName().substring(3); + + // Skip interface methods, these will be called explicitly + if (propName.equals(TableConstants.PARTITION_KEY) || propName.equals(TableConstants.ROW_KEY) + || propName.equals(TableConstants.TIMESTAMP) || propName.equals("Etag") + || propName.equals("LastModified")) { + continue; + } + + if (propMap.containsKey(propName)) { + currProperty = propMap.get(propName); + } + else { + currProperty = new PropertyPair(); + currProperty.name = propName; + propMap.put(propName, currProperty); + } + + // TODO add logging + // System.out.println(m.getReturnType()); + if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) { + currProperty.getter = m; + } + else if (m.getName().startsWith("set") && m.getParameterTypes().length == 1 + && void.class.equals(m.getReturnType())) { + currProperty.setter = m; + } + + // Check for StoreAs Annotation + final StoreAs storeAsInstance = m.getAnnotation(StoreAs.class); + if (storeAsInstance != null) { + if (Utility.isNullOrEmpty(storeAsInstance.name())) { + throw new IllegalArgumentException(String.format( + "StoreAs Annotation found for property %s with empty value", currProperty.name)); + } + + if (currProperty.effectiveName != null && !currProperty.effectiveName.equals(currProperty.name) + && !currProperty.effectiveName.equals(storeAsInstance.name())) { + throw new IllegalArgumentException( + String.format( + "StoreAs Annotation found for both getter and setter for property %s with non equal values", + currProperty.name)); + } + + if (!currProperty.name.equals(storeAsInstance.name())) { + currProperty.effectiveName = storeAsInstance.name(); + } + } + } + + // Return only processable pairs + final ArrayList<String> keysToRemove = new ArrayList<String>(); + final ArrayList<String> keysToAlter = new ArrayList<String>(); + + for (final Entry<String, PropertyPair> e : propMap.entrySet()) { + if (!e.getValue().shouldProcess()) { + keysToRemove.add(e.getKey()); + continue; + } + + if (!Utility.isNullOrEmpty(e.getValue().effectiveName)) { + keysToAlter.add(e.getKey()); + } + else { + e.getValue().effectiveName = e.getValue().name; + } + } + + // remove all entries for keys that should not process + for (final String key : keysToRemove) { + propMap.remove(key); + } + + // Any store as properties should be re-stored into the hash under the efective name. + for (final String key : keysToAlter) { + final PropertyPair p = propMap.get(key); + propMap.remove(key); + propMap.put(p.effectiveName, p); + } + + return propMap; + } + + private Method getter = null; + private Method setter = null; + private String name = null; + String effectiveName = null; + + /** + * Reserved for internal use. Invokes the setter method on the specified instance parameter with the value of the + * {@link EntityProperty} deserialized as the appropriate type. + * + * @param prop + * The {@link EntityProperty} containing the value to pass to the setter on the instance. + * @param instance + * An instance of a class supporting this property with getter and setter methods of the + * appropriate name and parameter or return type. + * + * @throws IllegalArgumentException + * if the specified instance parameter is not an instance of the class + * or interface declaring the setter method (or of a subclass or implementor thereof). + * @throws IllegalAccessException + * if the setter method is inaccessible. + * @throws InvocationTargetException + * if the setter method throws an exception. + */ + protected void consumeTableProperty(final EntityProperty prop, final Object instance) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if (prop.getEdmType() == EdmType.STRING) { + this.setter.invoke(instance, prop.getValueAsString()); + } + else if (prop.getEdmType() == EdmType.BINARY) { + if (this.setter.getParameterTypes()[0].equals(Byte[].class)) { + this.setter.invoke(instance, (Object) prop.getValueAsByteObjectArray()); + } + else { + this.setter.invoke(instance, prop.getValueAsByteArray()); + } + } + else if (prop.getEdmType() == EdmType.BOOLEAN) { + this.setter.invoke(instance, prop.getValueAsBoolean()); + } + else if (prop.getEdmType() == EdmType.DOUBLE) { + this.setter.invoke(instance, prop.getValueAsDouble()); + } + else if (prop.getEdmType() == EdmType.GUID) { + this.setter.invoke(instance, prop.getValueAsUUID()); + } + else if (prop.getEdmType() == EdmType.INT32) { + this.setter.invoke(instance, prop.getValueAsInteger()); + } + else if (prop.getEdmType() == EdmType.INT64) { + this.setter.invoke(instance, prop.getValueAsLong()); + } + else if (prop.getEdmType() == EdmType.DATE_TIME) { + this.setter.invoke(instance, prop.getValueAsDate()); + } + else { + throw new IllegalArgumentException(String.format("Property %s with Edm Type %s cannot be de-serialized.", + this.name, prop.getEdmType().toString())); + } + } + + /** + * Reserved for internal use. Generates an {@link EntityProperty} from the result of invoking the getter method for + * this property on the specified instance parameter. + * + * @param instance + * An instance of a class supporting this property with getter and setter methods of the + * appropriate name and parameter or return type. + * + * @return + * An {@link EntityProperty} with the data type and value returned by the invoked getter on the instance. + * + * @throws IllegalArgumentException + * if the specified instance parameter is not an instance of the class + * or interface declaring the getter method (or of a subclass or implementor thereof). + * @throws IllegalAccessException + * if the getter method is inaccessible. + * @throws InvocationTargetException + * if the getter method throws an exception. + */ + protected EntityProperty generateTableProperty(final Object instance) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + final Class<?> getType = this.getter.getReturnType(); + Object val = this.getter.invoke(instance, (Object[]) null); + + if (getType.equals(byte[].class)) { + return val != null ? new EntityProperty((byte[]) val) : new EntityProperty(EdmType.BINARY); + } + else if (getType.equals(Byte[].class)) { + return val != null ? new EntityProperty((Byte[]) val) : new EntityProperty(EdmType.BINARY); + } + else if (getType.equals(String.class)) { + return val != null ? new EntityProperty((String) val) : new EntityProperty(EdmType.STRING); + } + else if (getType.equals(boolean.class) || getType.equals(Boolean.class)) { + return val != null ? new EntityProperty((Boolean) val) : new EntityProperty(EdmType.BOOLEAN); + } + else if (getType.equals(double.class) || getType.equals(Double.class)) { + return val != null ? new EntityProperty((Double) val) : new EntityProperty(EdmType.DOUBLE); + } + else if (getType.equals(UUID.class)) { + return val != null ? new EntityProperty((UUID) val) : new EntityProperty(EdmType.GUID); + } + else if (getType.equals(int.class) || getType.equals(Integer.class)) { + return val != null ? new EntityProperty((Integer) val) : new EntityProperty(EdmType.INT32); + } + else if (getType.equals(long.class) || getType.equals(Long.class)) { + return val != null ? new EntityProperty((Long) val) : new EntityProperty(EdmType.INT64); + } + else if (getType.equals(Date.class)) { + return val != null ? new EntityProperty((Date) val) : new EntityProperty(EdmType.DATE_TIME); + } + else { + throw new IllegalArgumentException(String.format("Property %s with return type %s cannot be serialized.", + this.getter.getName(), this.getter.getReturnType())); + } + } + + /** + * Reserved for internal use. A utility function that returns <code>true</code> if this property is accessible + * through reflection. + * + * @return + */ + protected boolean shouldProcess() { + if (Utility.isNullOrEmpty(this.name) || this.getter == null || this.getter.isAnnotationPresent(Ignore.class) + || this.setter == null || this.setter.isAnnotationPresent(Ignore.class) + || (!this.getter.getReturnType().equals(this.setter.getParameterTypes()[0]))) { + return false; + } + + // TODO add logging + // System.out.println("Valid property " + this.name + " Storing as " + this.effectiveName); + return true; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java new file mode 100644 index 0000000000000..52dbf5ecdccb3 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java @@ -0,0 +1,268 @@ +/** + * Copyright 2011 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.net.HttpURLConnection; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +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 that extends {@link TableOperation} to implement a query to retrieve a single table entity. To execute a + * {@link QueryTableOperation} instance, call the <code>execute</code> method on a {@link CloudTableClient} instance. + * This operation can be executed directly or as part of a {@link TableBatchOperation}. If the + * {@link QueryTableOperation} returns an entity result, it is stored in the corresponding {@link TableResult} returned + * by the <code>execute</code> method. + */ +public class QueryTableOperation extends TableOperation { + private EntityResolver<?> resolver; + + private Class<? extends TableEntity> clazzType; + + private String partitionKey; + + private String rowKey; + + /** + * Default constructor. + */ + protected QueryTableOperation() { + super(null, TableOperationType.RETRIEVE); + } + + /** + * Constructs a {@link QueryTableOperation} instance to retrieve a single table entity with the specified partition + * key and row key. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity. + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity. + */ + QueryTableOperation(final String partitionKey, final String rowKey) { + super(null, TableOperationType.RETRIEVE); + Utility.assertNotNullOrEmpty("partitionKey", partitionKey); + this.partitionKey = partitionKey; + this.rowKey = rowKey; + } + + /** + * Gets the PartitionKey value for the entity to retrieve. + * + * @return + * A <code>String</code> containing the PartitionKey value for the entity. + */ + public String getPartitionKey() { + return this.partitionKey; + } + + /** + * Gets the resolver to project the entity retrieved as a particular type. + * + * @return + * The {@link EntityResolver} instance. + */ + public EntityResolver<?> getResolver() { + return this.resolver; + } + + /** + * Gets the RowKey value for the entity to retrieve. + * + * @return + * A <code>String</code> containing the RowKey value for the entity. + */ + public String getRowKey() { + return this.rowKey; + } + + /** + * Reserved for internal use. Gets the class type of the entity returned by the query. + * + * @return + * The <code>java.lang.Class</code> implementing {@link TableEntity} that represents the entity type for the + * query. + */ + protected Class<? extends TableEntity> getClazzType() { + return this.clazzType; + } + + /** + * Reserved for internal use. Parses the query table operation response into a {@link TableResult} to return. + * + * @param xmlr + * An <code>XMLStreamReader</code> containing the response to the query operation. + * @param httpStatusCode + * The HTTP status code returned from the operation request. + * @param etagFromHeader + * The <code>String</code> containing the Etag returned with the operation response. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * + * @return + * The {@link TableResult} representing the result of the query operation. + * + * @throws XMLStreamException + * if an error occurs accessing the <code>XMLStreamReader</code>. + * @throws ParseException + * if an error occurs in parsing the response. + * @throws InstantiationException + * if an error occurs in object construction. + * @throws IllegalAccessException + * if an error occurs in reflection on an object type. + * @throws StorageException + * if an error occurs in the storage operation. + */ + @Override + protected TableResult parseResponse(final XMLStreamReader xmlr, final int httpStatusCode, + final String etagFromHeader, final OperationContext opContext) throws XMLStreamException, ParseException, + InstantiationException, IllegalAccessException, StorageException { + return AtomPubParser.parseSingleOpResponse(xmlr, httpStatusCode, this.getClazzType(), this.getResolver(), + opContext); + } + + /** + * Reserved for internal use. Performs a retrieve 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 and storage account + * credentials to use. + * @param tableName + * A <code>String</code> containing the name of the table to query. + * @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 query operation. + * + * @throws StorageException + * if an error occurs in the storage operation. + */ + protected TableResult performRetrieve(final CloudTableClient client, final String tableName, + final TableRequestOptions options, final OperationContext opContext) throws StorageException { + final boolean isTableEntry = TableConstants.TABLES_SERVICE_TABLES_NAME.equals(tableName); + if (this.getClazzType() != null) { + Utility.checkNullaryCtor(this.getClazzType()); + } + else { + Utility.assertNotNull("Query requires a valid class type or resolver.", this.getResolver()); + } + + final StorageOperation<CloudTableClient, QueryTableOperation, TableResult> impl = new StorageOperation<CloudTableClient, QueryTableOperation, TableResult>( + options) { + @Override + public TableResult execute(final CloudTableClient client, final QueryTableOperation operation, + final OperationContext opContext) throws Exception { + + final HttpURLConnection request = TableRequest.query(client.getEndpoint(), tableName, + generateRequestIdentity(isTableEntry, operation.getPartitionKey()), + options.getTimeoutIntervalInMs(), null/* Query Builder */, null/* Continuation Token */, + options, opContext); + + client.getCredentials().signRequestLite(request, -1L, opContext); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_OK) { + // Parse response for updates + InputStream inStream = request.getInputStream(); + final XMLStreamReader xmlr = Utility.createXMLStreamReaderFromStream(inStream); + TableResult res = null; + + try { + res = AtomPubParser.parseSingleOpResponse(xmlr, this.getResult().getStatusCode(), + operation.getClazzType(), operation.getResolver(), opContext); + } + finally { + inStream.close(); + } + + return res; + } + else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + // Empty result + return new TableResult(this.getResult().getStatusCode()); + } + else { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + } + }; + + return ExecutionEngine.executeWithRetry(client, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Reserved for internal use. Sets the class type of the entity returned by the query. + * + * @param clazzType + * The <code>java.lang.Class</code> implementing {@link TableEntity} that represents the entity type for + * the query. + */ + protected void setClazzType(final Class<? extends TableEntity> clazzType) { + Utility.assertNotNull("clazzType", clazzType); + Utility.checkNullaryCtor(clazzType); + this.clazzType = clazzType; + } + + /** + * Reserved for internal use. Sets the PartitionKey value for the entity to retrieve. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity. + */ + protected void setPartitionKey(final String partitionKey) { + this.partitionKey = partitionKey; + } + + /** + * Reserved for internal use. Sets the resolver to project the entity retrieved as a particular type. + * + * @param resolver + * The {@link EntityResolver} instance to use. + */ + protected void setResolver(final EntityResolver<?> resolver) { + Utility.assertNotNull("Query requires a valid class type or resolver.", resolver); + this.resolver = resolver; + } + + /** + * Reserved for internal use. Sets the RowKey value for the entity to retrieve. + * + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity. + */ + protected void setRowKey(final String rowKey) { + this.rowKey = rowKey; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/StoreAs.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/StoreAs.java new file mode 100644 index 0000000000000..7918530c85f34 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/StoreAs.java @@ -0,0 +1,49 @@ +/** + * Copyright 2011 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.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to override the name a property is serialized and deserialized with using reflection. Use this + * annotation to specify the property name to associate with the data stored by a setter method or retrieved by a getter + * method in a class implementing {@link TableEntity} that uses reflection-based serialization and deserialization. Note + * that the names "PartitionKey", "RowKey", "Timestamp", and "Etag" are reserved and will be ignored if set with the + * <code>@StoreAs</code> annotation. + * <p> + * Example: + * <p> + * <code>@StoreAs(name = "EntityPropertyName")<br>public String getObjectPropertyName() { ... }</code> + * <p> + * <code>@StoreAs(name = "EntityPropertyName")<br>public void setObjectPropertyName(String name) { ... }</code> + * <p> + * This example shows how the methods that would get and set an entity property named <em>ObjectPropertyName</em> in the + * default case can be annotated to get and set an entity property named <em>EntityPropertyName</em>. See the + * documentation for {@link TableServiceEntity} for more information on using reflection-based serialization and + * deserialization. + * + * @see Ignore + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface StoreAs { + public String name(); +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java new file mode 100644 index 0000000000000..596e519612c30 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java @@ -0,0 +1,514 @@ +/** + * Copyright 2011 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 <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Entity Group Transaction</a>. + * <p> + * 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<TableOperation> { + 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 <code>ArrayList</code>. + * + * @param index + * The index in the batch operation <code>ArrayList</code> 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 <code>ArrayList</code>. + * + * @param element + * The {@link TableOperation} to add to the batch operation. + * @return + * <code>true</code> 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 <code>ArrayList</code> starting at the specified + * index. + * + * @param index + * The index in the batch operation <code>ArrayList</code> to add the table operation at. + * @param c + * The collection of {@link TableOperation} objects to add to the batch operation. + * @return + * <code>true</code> if the operations were added successfully. + */ + @Override + public boolean addAll(final int index, final java.util.Collection<? extends TableOperation> 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 <code>ArrayList</code>. + * + * @param c + * The collection of {@link TableOperation} objects to add to the batch operation. + * @return + * <code>true</code> if the operations were added successfully. + */ + @Override + public boolean addAll(final java.util.Collection<? extends TableOperation> 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 <code>String</code> containing the PartitionKey of the entity to retrieve. + * @param rowKey + * A <code>String</code> 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<? extends TableEntity> 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 <code>String</code> containing the PartitionKey of the entity to retrieve. + * @param rowKey + * A <code>String</code> 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 <code>ArrayList</code> 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 <code>Object</code> from the batch operation. + * + * @param o + * The <code>Object</code> to remove from the batch operation. + * @return + * <code>true</code> 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 + * <code>true</code> 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 <code>String</code> 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}. + * <p> + * 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 <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 + * An <code>ArrayList</code> of {@link TableResult} containing the results of executing the operation. + * + * @throws StorageException + * if an error occurs in the storage operation. + */ + protected ArrayList<TableResult> 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<CloudTableClient, TableBatchOperation, ArrayList<TableResult>> impl = new StorageOperation<CloudTableClient, TableBatchOperation, ArrayList<TableResult>>( + options) { + @Override + public ArrayList<TableResult> 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.getEndpoint(), + 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); + ArrayList<MimePart> responseParts = null; + try { + this.setResult(opContext.getLastResult()); + 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<TableResult> result = new ArrayList<TableResult>(); + 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 <code>ArrayList</code>. + * + * @param fromIndex + * The inclusive lower bound of the range of {@link TableOperation} objects to remove from the batch + * operation <code>ArrayList</code>. + * @param toIndex + * The exclusive upper bound of the range of {@link TableOperation} objects to remove from the batch + * operation <code>ArrayList</code>. + */ + @Override + protected void removeRange(int fromIndex, int toIndex) { + super.removeRange(fromIndex, toIndex); + checkResetEntityLocks(); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableConstants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableConstants.java new file mode 100644 index 0000000000000..48d3f3d56ea09 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableConstants.java @@ -0,0 +1,143 @@ +/** + * Copyright 2011 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; + +/** + * Holds the constants used for the Table Service. + */ +public final class TableConstants { + /** + * The constants used in HTML header fields for Table service requests. + */ + public static class HeaderConstants { + /** + * The ETag header field label. + */ + public static final String ETAG = "ETag"; + + /** + * The Accept header value to send. + */ + public static final String ACCEPT_TYPE = "application/atom+xml,application/xml"; + + /** + * The Content-Type header value to send for single operations. + */ + public static final String ATOMPUB_TYPE = "application/atom+xml"; + + /** + * The Content-Type header value to send for batch operations. + */ + public static final String MULTIPART_MIXED_FORMAT = "multipart/mixed; boundary=%s"; + + /** + * The DataServiceVersion header field label. + */ + public static final String DATA_SERVICE_VERSION = "DataServiceVersion"; + + /** + * The DataServiceVersion header value to send. + */ + public static final String DATA_SERVICE_VERSION_VALUE = "1.0;NetFx"; + + /** + * The MaxDataServiceVersion header field label. + */ + public static final String MAX_DATA_SERVICE_VERSION = "MaxDataServiceVersion"; + + /** + * The MaxDataServiceVersion header value to send. + */ + public static final String MAX_DATA_SERVICE_VERSION_VALUE = "2.0;NetFx"; + } + + /** + * Default client side timeout, in milliseconds, for table clients. + */ + public static final int TABLE_DEFAULT_TIMEOUT_IN_MS = 60 * 1000; + + /** + * Stores the header prefix for continuation information. + */ + public static final String TABLE_SERVICE_PREFIX_FOR_TABLE_CONTINUATION = "x-ms-continuation-"; + + /** + * Stores the header suffix for the next partition key. + */ + public static final String TABLE_SERVICE_NEXT_PARTITION_KEY = "NextPartitionKey"; + + /** + * Stores the header suffix for the next row key. + */ + public static final String TABLE_SERVICE_NEXT_ROW_KEY = "NextRowKey"; + + /** + * Stores the header suffix for the next marker. + */ + public static final String TABLE_SERVICE_NEXT_MARKER = "NextMarker"; + + /** + * Stores the table suffix for the next table name. + */ + public static final String TABLE_SERVICE_NEXT_TABLE_NAME = "NextTableName"; + + /** + * The name of the partition key property. + */ + public static final String PARTITION_KEY = "PartitionKey"; + + /** + * The name of the row key property. + */ + public static final String ROW_KEY = "RowKey"; + + /** + * The name of the Timestamp property. + */ + public static final String TIMESTAMP = "Timestamp"; + + /** + * The name of the special table used to store tables. + */ + public static final String TABLES_SERVICE_TABLES_NAME = "Tables"; + + /** + * The name of the property that stores the table name. + */ + public static final String TABLE_NAME = "TableName"; + + /** + * The query filter clause name. + */ + public static final String FILTER = "$filter"; + + /** + * The query top clause name. + */ + public static final String TOP = "$top"; + + /** + * The query select clause name. + */ + public static final String SELECT = "$select"; + + /** + * Private Default Constructor. + */ + private TableConstants() { + // No op + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableEntity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableEntity.java new file mode 100644 index 0000000000000..85dc20a8c37bf --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableEntity.java @@ -0,0 +1,170 @@ +/** + * Copyright 2011 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.util.Date; +import java.util.HashMap; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * An interface required for table entity types. The {@link TableEntity} interface declares getter and setter methods + * for the common entity properties, and <code>writeEntity</code> and <code>readEntity</code> methods for serialization + * and deserialization of all entity properties using a property map. Create classes implementing {@link TableEntity} to + * customize property storage, retrieval, serialization and deserialization, and to provide additional custom logic for + * a table entity. + * <p> + * The Storage client library includes two implementations of {@link TableEntity} that provide for simple property + * access and serialization: + * <p> + * {@link DynamicTableEntity} implements {@link TableEntity} and provides a simple property map to store and retrieve + * properties. Use a {@link DynamicTableEntity} for simple access to entity properties when only a subset of properties + * are returned (for example, by a select clause in a query), or for when your query can return multiple entity types + * with different properties. You can also use this type to perform bulk table updates of heterogeneous entities without + * losing property information. + * <p> + * {@link TableServiceEntity} is an implementation of {@link TableEntity} that uses reflection-based serialization and + * deserialization behavior in its <code>writeEntity</code> and <code>readEntity</code> methods. + * {@link TableServiceEntity}-derived classes with methods that follow a convention for types and naming are serialized + * and deserialized automatically. + * <p> + * Any class that implements {@link TableEntity} can take advantage of the automatic reflection-based serialization and + * deserialization behavior in {@link TableServiceEntity} by invoking the static methods + * <code>TableServiceEntity.readEntityWithReflection</code> in <code>readEntity</code> and + * <code>TableServiceEntity.writeEntityWithReflection</code> in <code>writeEntity</code>. The class must provide methods + * that follow the type and naming convention to be serialized and deserialized automatically. When both a getter method + * and setter method are found for a given property name and data type, then the appropriate method is invoked + * automatically to serialize or deserialize the data. The reflection code looks for getter and setter methods in pairs + * of the form + * <p> + * <code>public <em>type</em> get<em>PropertyName</em>() { ... }</code> + * <p> + * and + * <p> + * <code>public void set<em>PropertyName</em>(<em>type</em> parameter) { ... }</code> + * <p> + * where <em>PropertyName</em> is a property name for the table entity, and <em>type</em> is a Java type compatible with + * the EDM data type of the property. See the table in the class description for {@link TableServiceEntity} for a map of + * property types to their Java equivalents. The {@link StoreAs} annotation may be applied with a <code>name</code> + * attribute to specify a property name for reflection on getter and setter methods that do not follow the property name + * convention. Method names and the <code>name</code> attribute of {@link StoreAs} annotations are case sensitive for + * matching property names with reflection. Use the {@link Ignore} annotation to prevent methods from being used by + * reflection for automatic serialization and deserialization. Note that the names "PartitionKey", "RowKey", + * "Timestamp", and "Etag" are reserved and will be ignored if set with the {@link StoreAs} annotation in a subclass + * that uses the reflection methods. + * <p> + * + * @see TableServiceEntity + * @see DynamicTableEntity + */ +public interface TableEntity { + + /** + * Gets the Etag value for the entity. This value is used to determine if the table entity has changed since it was + * last read from Windows Azure storage. + * + * @return + * A <code>String</code> containing the Etag for the entity. + */ + public String getEtag(); + + /** + * Gets the PartitionKey value for the entity. + * + * @return + * A <code>String</code> containing the PartitionKey value for the entity. + */ + public String getPartitionKey(); + + /** + * Gets the RowKey value for the entity. + * + * @return + * A <code>String</code> containing the RowKey value for the entity. + */ + public String getRowKey(); + + /** + * Gets the Timestamp for the entity. + * + * @return + * A <code>Date</code> containing the Timestamp value for the entity. + */ + public Date getTimestamp(); + + /** + * Populates an instance of the object implementing {@link TableEntity} using the specified properties parameter, + * containing a map of <code>String</code> property names to {@link EntityProperty} data typed values. + * + * @param properties + * The <code>java.util.HashMap</code> of <code>String</code> to {@link EntityProperty} data typed values + * to use to populate the table entity instance. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * @throws StorageException + * if an error occurs during the operation. + */ + public void readEntity(HashMap<String, EntityProperty> properties, OperationContext opContext) + throws StorageException; + + /** + * Sets the Etag for the entity. + * + * @param etag + * The <code>String</code> containing the Etag to set for the entity. + */ + public void setEtag(String etag); + + /** + * Sets the PartitionKey value for the entity. + * + * @param partitionKey + * The <code>String</code> containing the PartitionKey value to set for the entity. + */ + public void setPartitionKey(String partitionKey); + + /** + * Sets the RowKey value for the entity. + * + * @param rowKey + * The <code>String</code> containing the RowKey value to set for the entity. + */ + public void setRowKey(String rowKey); + + /** + * Sets the Timestamp value for the entity. + * + * @param timeStamp + * The <code>Date</code> containing the Timestamp value to set for the entity. + */ + public void setTimestamp(Date timeStamp); + + /** + * Returns a map of <code>String</code> property names to {@link EntityProperty} data typed values + * that represents the serialized content of the table entity instance. + * + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * @return + * The <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} data + * typed values representing the properties of the table entity. + * + * @throws StorageException + * if an error occurs during the operation. + */ + public HashMap<String, EntityProperty> writeEntity(OperationContext opContext) throws StorageException; +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java new file mode 100644 index 0000000000000..c9c72e0856ddc --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java @@ -0,0 +1,699 @@ +/** + * Copyright 2011 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.net.HttpURLConnection; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamException; +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.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 single table operation. + * <p> + * Use the static factory methods to construct {@link TableOperation} instances for operations on tables that insert, + * update, merge, delete, replace or retrieve table entities. To execute a {@link TableOperation} instance, call the + * <code>execute</code> method on a {@link CloudTableClient} instance. A {@link TableOperation} may be executed directly + * or as part of a {@link TableBatchOperation}. If a {@link TableOperation} returns an entity result, it is stored in + * the corresponding {@link TableResult} returned by the <code>execute</code> method. + * + */ +public class TableOperation { + /** + * A static factory method returning a {@link TableOperation} instance to delete the specified entity from Windows + * Azure storage. To execute this {@link TableOperation} on a given table, call the + * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the + * table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance to insert the table entity. + */ + public static TableOperation delete(final TableEntity entity) { + Utility.assertNotNull("Entity", entity); + Utility.assertNotNullOrEmpty("Entity Etag", entity.getEtag()); + return new TableOperation(entity, TableOperationType.DELETE); + } + + /** + * A static factory method returning a {@link TableOperation} instance to insert the specified entity into Windows + * Azure storage. To execute this {@link TableOperation} on a given table, call the + * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the + * table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance to insert the table entity. + */ + public static TableOperation insert(final TableEntity entity) { + Utility.assertNotNull("Entity", entity); + return new TableOperation(entity, TableOperationType.INSERT); + } + + /** + * A static factory method returning a {@link TableOperation} instance to merge the specified entity into Windows + * Azure storage, or insert it if it does not exist. To execute this {@link TableOperation} on a given table, call + * the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with + * the table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance for inserting or merging the table entity. + */ + public static TableOperation insertOrMerge(final TableEntity entity) { + Utility.assertNotNull("Entity", entity); + return new TableOperation(entity, TableOperationType.INSERT_OR_MERGE); + } + + /** + * A static factory method returning a {@link TableOperation} instance to replace the specified entity in Windows + * Azure storage, or insert it if it does not exist. To execute this {@link TableOperation} on a given table, call + * the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with + * the table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance for inserting or replacing the table entity. + */ + public static TableOperation insertOrReplace(final TableEntity entity) { + Utility.assertNotNull("Entity", entity); + return new TableOperation(entity, TableOperationType.INSERT_OR_REPLACE); + } + + /** + * A static factory method returning a {@link TableOperation} instance to merge the specified table entity into + * Windows Azure storage. To execute this {@link TableOperation} on a given table, call the + * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the + * table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance for merging the table entity. + */ + public static TableOperation merge(final TableEntity entity) { + Utility.assertNotNull("Entity", entity); + Utility.assertNotNullOrEmpty("Entity Etag", entity.getEtag()); + return new TableOperation(entity, TableOperationType.MERGE); + } + + /** + * A static factory method returning a {@link TableOperation} instance to retrieve the specified table entity and + * return it as the specified type. To execute this {@link TableOperation} on a given table, call the + * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the + * table name and the {@link TableOperation} as arguments. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity to retrieve. + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity to retrieve. + * @param clazzType + * The class type of the table entity object to retrieve. + * @return + * A new {@link TableOperation} instance for retrieving the table entity. + */ + public static TableOperation retrieve(final String partitionKey, final String rowKey, + final Class<? extends TableEntity> clazzType) { + final QueryTableOperation retOp = new QueryTableOperation(partitionKey, rowKey); + retOp.setClazzType(clazzType); + return retOp; + } + + /** + * A static factory method returning a {@link TableOperation} instance to retrieve the specified table entity and + * return a projection of it using the specified resolver. To execute this {@link TableOperation} on a given table, + * call the {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance + * with the table name and the {@link TableOperation} as arguments. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity to retrieve. + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity to retrieve. + * @param resolver + * The implementation of {@link EntityResolver} to use to project the result entity as type T. + * @return + * A new {@link TableOperation} instance for retrieving the table entity. + */ + public static TableOperation retrieve(final String partitionKey, final String rowKey, + final EntityResolver<?> resolver) { + final QueryTableOperation retOp = new QueryTableOperation(partitionKey, rowKey); + retOp.setResolver(resolver); + return retOp; + } + + /** + * A static factory method returning a {@link TableOperation} instance to replace the specified table entity. To + * execute this {@link TableOperation} on a given table, call the + * {@link CloudTableClient#execute(String, TableOperation)} method on a {@link CloudTableClient} instance with the + * table name and the {@link TableOperation} as arguments. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @return + * A new {@link TableOperation} instance for replacing the table entity. + */ + public static TableOperation replace(final TableEntity entity) { + Utility.assertNotNullOrEmpty("Entity Etag", entity.getEtag()); + return new TableOperation(entity, TableOperationType.REPLACE); + } + + /** + * The table entity instance associated with the operation. + */ + TableEntity entity; + + /** + * The {@link TableOperationType} enumeration value for the operation type. + */ + TableOperationType opType = null; + + /** + * Nullary Default Constructor. + */ + protected TableOperation() { + // empty ctor + } + + /** + * Reserved for internal use. Constructs a {@link TableOperation} with the specified table entity and operation + * type. + * + * @param entity + * The object instance implementing {@link TableEntity} to associate with the operation. + * @param opType + * The {@link TableOperationType} enumeration value for the operation type. + */ + protected TableOperation(final TableEntity entity, final TableOperationType opType) { + this.entity = entity; + this.opType = opType; + } + + /** + * 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.getEndpoint(), tableName, + generateRequestIdentity(isTableEntry, tableIdentity), 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. 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.getEndpoint(), tableName, + generateRequestIdentity(isTableEntry, tableIdentity), + 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. 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.getEndpoint(), tableName, + generateRequestIdentity(false, null), 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); + } + + /** + * Reserved for internal use. Perform an update 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. + */ + private TableResult performUpdate(final CloudTableClient client, final String tableName, + final TableRequestOptions options, final OperationContext opContext) throws StorageException { + Utility.assertNotNullOrEmpty("Update requires a valid ETag", this.getEntity().getEtag()); + Utility.assertNotNullOrEmpty("Update requires a valid PartitionKey", this.getEntity().getPartitionKey()); + Utility.assertNotNullOrEmpty("Update 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.update(client.getEndpoint(), tableName, + generateRequestIdentity(false, null), 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); + } + + /** + * 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. Generates the request identity, consisting of the specified entry name, or the + * PartitionKey and RowKey pair from the operation, to identify the operation target. + * + * @param isSingleIndexEntry + * Pass <code>true</code> to use the specified <code>entryName</code> parameter, or <code>false</code> to + * use PartitionKey and RowKey values from the operation as the request identity. + * @param entryName + * The entry name to use as the request identity if the <code>isSingleIndexEntry</code> parameter is + * <code>true</code>. + * @return + * A <code>String</code> containing the formatted request identity string. + */ + protected String generateRequestIdentity(boolean isSingleIndexEntry, final String entryName) { + if (isSingleIndexEntry) { + return String.format("'%s'", entryName); + } + + if (this.opType == TableOperationType.INSERT) { + return Constants.EMPTY_STRING; + } + else { + String pk = null; + String rk = null; + + if (this.opType == TableOperationType.RETRIEVE) { + final QueryTableOperation qOp = (QueryTableOperation) this; + pk = qOp.getPartitionKey(); + rk = qOp.getRowKey(); + } + else { + pk = this.getEntity().getPartitionKey(); + rk = this.getEntity().getRowKey(); + } + + return String.format("%s='%s',%s='%s'", TableConstants.PARTITION_KEY, pk, TableConstants.ROW_KEY, rk); + } + } + + /** + * Reserved for internal use. Generates the request identity string for the specified table. The request identity + * string combines the table name with the PartitionKey and RowKey from the operation to identify specific table + * entities. + * + * @param tableName + * A <code>String</code> containing the name of the table. + * @return + * A <code>String</code> containing the formatted request identity string for the specified table. + */ + protected String generateRequestIdentityWithTable(final String tableName) { + return String.format("/%s(%s)", tableName, generateRequestIdentity(false, null)); + } + + /** + * Reserved for internal use. Gets the table entity associated with this operation. + * + * @return + * The {@link TableEntity} instance associated with this operation. + */ + protected synchronized final TableEntity getEntity() { + return this.entity; + } + + /** + * Reserved for internal use. Gets the operation type for this operation. + * + * @return the opType + * The {@link TableOperationType} instance associated with this operation. + */ + protected synchronized final TableOperationType getOperationType() { + return this.opType; + } + + /** + * Reserved for internal use. Parses the table operation response into a {@link TableResult} to return. + * + * @param xmlr + * An <code>XMLStreamReader</code> containing the response to an insert operation. + * @param httpStatusCode + * The HTTP status code returned from the operation request. + * @param etagFromHeader + * The <code>String</code> containing the Etag returned with the operation response. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * + * @return + * The {@link TableResult} representing the result of the operation. + * + * @throws XMLStreamException + * if an error occurs accessing the <code>XMLStreamReader</code>. + * @throws ParseException + * if an error occurs in parsing the response. + * @throws InstantiationException + * if an error occurs in object construction. + * @throws IllegalAccessException + * if an error occurs in reflection on an object type. + * @throws StorageException + * if an error occurs in the storage operation. + */ + protected TableResult parseResponse(final XMLStreamReader xmlr, final int httpStatusCode, + final String etagFromHeader, final OperationContext opContext) throws XMLStreamException, ParseException, + InstantiationException, IllegalAccessException, StorageException { + TableResult resObj = null; + if (this.opType == TableOperationType.INSERT) { + // Sending null for class type and resolver will ignore parsing the return payload. + resObj = AtomPubParser.parseSingleOpResponse(xmlr, httpStatusCode, null, null, opContext); + resObj.updateResultObject(this.getEntity()); + } + else { + resObj = new TableResult(httpStatusCode); + resObj.setResult(this.getEntity()); + + if (this.opType != TableOperationType.DELETE) { + this.getEntity().setEtag(etagFromHeader); + } + } + + return resObj; + } + + /** + * Reserved for internal use. Sets the {@link TableEntity} instance for the table operation. + * + * @param entity + * The {@link TableEntity} instance to set. + */ + protected synchronized final void setEntity(final TableEntity entity) { + this.entity = entity; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperationType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperationType.java new file mode 100644 index 0000000000000..a1ceccabd3320 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperationType.java @@ -0,0 +1,43 @@ +/** + * Copyright 2011 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; + +/** + * Reserved for internal use. An enumeration type which represents the type of operation a {@link TableOperation} + * represents. + */ +enum TableOperationType { + INSERT, DELETE, REPLACE, RETRIEVE, MERGE, INSERT_OR_REPLACE, INSERT_OR_MERGE; + + /** + * Gets the {@link TableUpdateType} associated the operation type, if applicable. Applies to + * {@link #INSERT_OR_MERGE} and {@link #INSERT_OR_REPLACE} values. + * + * @return + * The applicable {@link TableUpdateType}, or <code>null</code>. + */ + public TableUpdateType getUpdateType() { + if (this == INSERT_OR_MERGE) { + return TableUpdateType.MERGE; + } + else if (this == INSERT_OR_REPLACE) { + return TableUpdateType.REPLACE; + } + else { + return null; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java new file mode 100644 index 0000000000000..a54279ae0ce17 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java @@ -0,0 +1,773 @@ +/** + * Copyright 2011 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.util.Date; +import java.util.Formatter; +import java.util.UUID; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * A class which represents a query against a specified table. A {@link TableQuery} instance aggregates the query + * parameters to use when the query is executed. One of the <code>execute</code> or <code>executeSegmented</code> + * methods of {@link CloudTableClient} must be called to execute the query. The parameters are encoded and passed to the + * server when the table query is executed. + * <p> + * To create a table query with fluent syntax, the {@link #from} static factory method and the {@link #where}, + * {@link #select}, and {@link #take} mutator methods each return a reference to the object which can be chained into a + * single expression. Use the {@link TableQuery#from(String, Class)} static class factory method to create a + * <code>TableQuery</code> instance that executes on the named table with entities of the specified {@link TableEntity} + * implementing type. Use the {@link #where} method to specify a filter expression for the entities returned. Use the + * {@link #select} method to specify the table entity properties to return. Use the {@link #take} method to limit the + * number of entities returned by the query. Note that nothing prevents calling these methods more than once on a + * <code>TableQuery</code>, so the values saved in the <code>TableQuery</code> will be the last encountered in order of + * execution. + * <p> + * As an example, you could construct a table query using fluent syntax: + * <p> + * <code>TableQuery<TableServiceEntity> myQuery = TableQuery.from("Products", DynamicTableEntity.class)<br> + *     .where("(PartitionKey eq 'ProductsMNO') and (RowKey ge 'Napkin')")<br> + *     .take(25)<br> + *     .select(new String[] {"InventoryCount"});</code> + * <p> + * This example creates a query on the "Products" table for all entities where the PartitionKey value is "ProductsMNO" + * and the RowKey value is greater than or equal to "Napkin" and requests the first 25 matching entities, selecting only + * the common properties and the property named "InventoryCount", and returns them as {@link DynamicTableEntity} + * objects. + * <p> + * Filter expressions for use with the {@link #where} method or {@link #setFilterString} method can be created using + * fluent syntax with the overloaded {@link #generateFilterCondition} methods and {@link #combineFilters} method, using + * the comparison operators defined in {@link QueryComparisons} and the logical operators defined in {@link Operators}. + * Note that the first operand in a filter comparison must be a property name, and the second operand must evaluate to a + * constant. The PartitionKey and RowKey property values are <code>String</code> types for comparison purposes. + * <p> + * The values that may be used in table queries are explained in more detail in the MSDN topic <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a>, but note + * that the space characters within values do not need to be URL-encoded, as this will be done when the query is + * executed. + * <p> + * The {@link TableQuery#TableQuery(String, Class)} constructor and {@link TableQuery#from(String, Class)} static + * factory methods require a class type which implements {@link TableEntity} and contains a nullary constructor. If the + * query will be executed using an {@link EntityResolver}, the caller may specify {@link TableServiceEntity} + * <code>.class</code> as the class type. + * + * @param <T> + * A class type which implements {@link TableEntity} and contains a nullary constructor. Note: when using an + * inner class to define the class type, mark the class as static. + */ +public class TableQuery<T extends TableEntity> { + /** + * A static class that maps identifiers to filter expression operators. + */ + public static class Operators { + /** + * And + */ + public static final String AND = "and"; + + /** + * Not + */ + public static final String NOT = "not"; + + /** + * Or + */ + public static final String OR = "or"; + } + + /** + * A static class that maps identifiers to filter property comparison operators. + */ + public static class QueryComparisons { + /** + * Equal + */ + public static final String EQUAL = "eq"; + + /** + * Not Equal + */ + public static final String NOT_EQUAL = "ne"; + + /** + * Greater Than + */ + public static final String GREATER_THAN = "gt"; + + /** + * Greater Than Or Equal + */ + public static final String GREATER_THAN_OR_EQUAL = "ge"; + + /** + * Less Than + */ + public static final String LESS_THAN = "lt"; + + /** + * Less Than Or Equal + */ + public static final String LESS_THAN_OR_EQUAL = "le"; + } + + /** + * A static factory method that constructs a {@link TableQuery} instance and defines its source table and + * table entity type. The method returns the {@link TableQuery} instance reference, allowing additional methods to + * be chained to modify the query. + * <p> + * The created {@link TableQuery} instance is specialized for table entities of the specified class type T, using + * the table with the specified name as data source. Callers may specify {@link TableServiceEntity} + * <code>.class</code> as the class type parameter if no more specialized type is required. + * + * @param tablename + * A <code>String</code> containing the name of the source table to query. + * @param clazzType + * The <code>java.lang.Class</code> of the class <code>T</code> implementing the {@link TableEntity} + * interface that + * represents the table entity type for the query. + * + * @return + * The {@link TableQuery} instance with the source table name and entity type specialization set. + */ + public static <T extends TableEntity> TableQuery<T> from(final String tablename, final Class<T> clazzType) { + return new TableQuery<T>(tablename, clazzType); + } + + /** + * Generates a property filter condition string for a <code>boolean</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as a + * boolean, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("BooleanProperty", QueryComparisons.EQUAL, false);</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>BooleanProperty eq false</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>boolean</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final boolean value) { + return generateFilterCondition(propertyName, operation, value ? Constants.TRUE : Constants.FALSE, + EdmType.BOOLEAN); + } + + /** + * Generates a property filter condition string for a <code>byte[]</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as a + * binary value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("ByteArray", QueryComparisons.EQUAL, new byte[] {0x01, 0x0f});</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>ByteArray eq X'010f'</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>byte[]</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final byte[] value) { + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb); + for (byte b : value) { + formatter.format("%02x", b); + } + + return generateFilterCondition(propertyName, operation, sb.toString(), EdmType.BINARY); + } + + /** + * Generates a property filter condition string for a <code>Byte[]</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as a + * binary value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("ByteArray", QueryComparisons.EQUAL, new Byte[] {0x01, 0xfe});</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>ByteArray eq X'01fe'</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>Byte[]</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final Byte[] value) { + StringBuilder sb = new StringBuilder(); + Formatter formatter = new Formatter(sb); + for (byte b : value) { + formatter.format("%02x", b); + } + + return generateFilterCondition(propertyName, operation, sb.toString(), EdmType.BINARY); + } + + /** + * Generates a property filter condition string for a <code>Date</code> value. Creates a formatted string to use in + * a filter expression that uses the specified operation to compare the property with the value, formatted as a + * datetime value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("FutureDate", QueryComparisons.GREATER_THAN, new Date());</code> + * <p> + * This statement sets <code>condition</code> to something like the following value: + * <p> + * <code>FutureDate gt datetime'2013-01-31T09:00:00'</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>Date</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final Date value) { + return generateFilterCondition(propertyName, operation, + Utility.getTimeByZoneAndFormat(value, Utility.UTC_ZONE, Utility.ISO8061_LONG_PATTERN), + EdmType.DATE_TIME); + } + + /** + * Generates a property filter condition string for a <code>double</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as + * a double value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("Circumference", QueryComparisons.EQUAL, 2 * 3.141592);</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>Circumference eq 6.283184</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>double</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final double value) { + return generateFilterCondition(propertyName, operation, Double.toString(value), EdmType.DOUBLE); + } + + /** + * Generates a property filter condition string for an <code>int</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as + * a numeric value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("Population", QueryComparisons.GREATER_THAN, 1000);</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>Population gt 1000</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * An <code>int</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final int value) { + return generateFilterCondition(propertyName, operation, Integer.toString(value), EdmType.INT32); + } + + /** + * Generates a property filter condition string for a <code>long</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as + * a numeric value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("StellarMass", QueryComparisons.GREATER_THAN, 7000000000L);</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>StellarMass gt 7000000000</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>long</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final long value) { + return generateFilterCondition(propertyName, operation, Long.toString(value), EdmType.INT64); + } + + /** + * Generates a property filter condition string for a <code>String</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as + * a string value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("Platform", QueryComparisons.EQUAL, "Windows Azure");</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>Platform eq 'Windows Azure'</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>String</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final String value) { + return generateFilterCondition(propertyName, operation, value, EdmType.STRING); + } + + /** + * Generates a property filter condition string. Creates a formatted string to use in a filter expression that uses + * the specified operation to compare the property with the value, formatted as the specified {@link EdmType}. + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>String</code> containing the value to compare with the property. + * @param edmType + * The {@link EdmType} to format the value as. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, String value, EdmType edmType) { + String valueOperand = null; + + if (edmType == EdmType.BOOLEAN || edmType == EdmType.DOUBLE || edmType == EdmType.INT32 + || edmType == EdmType.INT64) { + valueOperand = value; + } + else if (edmType == EdmType.DATE_TIME) { + valueOperand = String.format("datetime'%s'", value); + } + else if (edmType == EdmType.GUID) { + valueOperand = String.format("guid'%s'", value); + } + else if (edmType == EdmType.BINARY) { + valueOperand = String.format("X'%s'", value); + } + else { + valueOperand = String.format("'%s'", value); + } + + return String.format("%s %s %s", propertyName, operation, valueOperand); + } + + /** + * Generates a property filter condition string for a <code>UUID</code> value. Creates a formatted string to use + * in a filter expression that uses the specified operation to compare the property with the value, formatted as + * a UUID value, as in the following example: + * <p> + * <code>String condition = generateFilterCondition("Identity", QueryComparisons.EQUAL, UUID.fromString(</code> + * <code>"c9da6455-213d-42c9-9a79-3e9149a57833"));</code> + * <p> + * This statement sets <code>condition</code> to the following value: + * <p> + * <code>Identity eq guid'c9da6455-213d-42c9-9a79-3e9149a57833'</code> + * + * @param propertyName + * A <code>String</code> containing the name of the property to compare. + * @param operation + * A <code>String</code> containing the comparison operator to use. + * @param value + * A <code>UUID</code> containing the value to compare with the property. + * @return + * A <code>String</code> containing the formatted filter condition. + */ + public static String generateFilterCondition(String propertyName, String operation, final UUID value) { + return generateFilterCondition(propertyName, operation, value.toString(), EdmType.GUID); + } + + /** + * Creates a filter condition using the specified logical operator on two filter conditions. + * + * @param filterA + * A <code>String</code> containing the first formatted filter condition. + * @param operator + * A <code>String</code> containing <code>Operators.AND</code> or <code>Operators.OR</code>. + * @param filterB + * A <code>String</code> containing the first formatted filter condition. + * @return + * A <code>String</code> containing the combined filter expression. + */ + public static String combineFilters(String filterA, String operator, String filterB) { + return String.format("(%s) %s (%s)", filterA, operator, filterB); + } + + private Class<T> clazzType = null; + private String sourceTableName = null; + private String[] columns = null; + private Integer takeCount; + private String filterString = null; + + /** + * Initializes an empty {@link TableQuery} instance. This table query cannot be executed without + * setting a source table and a table entity type. + */ + public TableQuery() { + // empty ctor + } + + /** + * Initializes a {@link TableQuery} with the specified source table and table entity type. Callers may specify + * {@link TableServiceEntity}<code>.class</code> as the class type parameter if no more specialized type is + * required. + * + * @param tablename + * A <code>String</code> containing the name of the source table to query. + * @param clazzType + * The <code>java.lang.Class</code> of the class <code>T</code> that represents the table entity type for + * the query. Class <code>T</code> must be a type that implements {@link TableEntity} and has a nullary + * constructor, + */ + public TableQuery(final String tableName, final Class<T> clazzType) { + this.setSourceTableName(tableName); + this.setClazzType(clazzType); + } + + /** + * Gets the class type of the table entities returned by the query. + * + * @return + * The <code>java.lang.Class</code> of the class <code>T</code> that represents the table entity type for + * the query. + */ + public Class<T> getClazzType() { + return this.clazzType; + } + + /** + * Gets an array of the table entity property names specified in the table query. All properties in the table are + * returned by default if no property names are specified with a select clause in the table query. The table entity + * properties to return may be specified with a call to the {@link #setColumns} or {@link #select} methods with a + * array of property names as parameter. + * <p> + * Note that the system properties <code>PartitionKey</code>, <code>RowKey</code>, and <code>Timestamp</code> are + * automatically requested from the table service whether specified in the {@link TableQuery} or not. + * + * @return + * An array of <code>String</code> objects containing the property names of the table entity properties to + * return in the query. + */ + public String[] getColumns() { + return this.columns; + } + + /** + * Gets the class type of the table entities returned by the query. + * + * @return + * The <code>java.lang.Class</code> of the class <code>T</code> implementing the {@link TableEntity} + * interface that + * represents the table entity type for the query. + */ + public Class<T> getEntityClass() { + return this.clazzType; + } + + /** + * Gets the filter expression specified in the table query. All entities in the table are returned by + * default if no filter expression is specified in the table query. A filter for the entities to return may be + * specified with a call to the {@link #setFilterString} or {@link #where} methods. + * + * @return + * A <code>String</code> containing the filter expression used in the query. + */ + public String getFilterString() { + return this.filterString; + } + + /** + * Gets the name of the source table specified in the table query. + * + * @return + * A <code>String</code> containing the name of the source table used in the query. + */ + public String getSourceTableName() { + return this.sourceTableName; + } + + /** + * Gets the number of entities the query returns specified in the table query. If this value is not + * specified in a table query, a maximum of 1,000 entries will be returned. The number of entities to return may be + * specified with a call to the {@link #setTakeCount} or {@link #take} methods. + * <p> + * If the value returned by <code>getTakeCount</code> is greater than 1,000, the query will throw a + * {@link StorageException} when executed. + * + * @return + * The maximum number of entities for the table query to return. + */ + public Integer getTakeCount() { + return this.takeCount; + } + + /** + * Defines the property names of the table entity properties to return when the table query is executed. The + * <code>select</code> clause is optional on a table query, used to limit the table properties returned from the + * server. By default, a query will return all properties from the table entity. + * <p> + * Note that the system properties <code>PartitionKey</code>, <code>RowKey</code>, and <code>Timestamp</code> are + * automatically requested from the table service whether specified in the {@link TableQuery} or not. + * + * @param columns + * An array of <code>String</code> objects containing the property names of the table entity properties + * to return when the query is executed. + * + * @return + * A reference to the {@link TableQuery} instance with the table entity properties to return set. + */ + public TableQuery<T> select(final String[] columns) { + this.setColumns(columns); + return this; + } + + /** + * Sets the class type of the table entities returned by the query. A class type is required to execute a table + * query. + * <p> + * Callers may specify {@link TableServiceEntity}<code>.class</code> as the class type parameter if no more + * specialized type is required. + * + * @param clazzType + * The <code>java.lang.Class</code> of the class <code>T</code> that represents the table entity type for + * the query. Class <code>T</code> must be a type that implements {@link TableEntity} and has a nullary + * constructor, + */ + public void setClazzType(final Class<T> clazzType) { + Utility.assertNotNull("Query requires a valid class type.", clazzType); + Utility.checkNullaryCtor(clazzType); + this.clazzType = clazzType; + } + + /** + * Sets the property names of the table entity properties to return when the table query is executed. By default, a + * query will return all properties from the table entity. + * <p> + * Note that the system properties <code>PartitionKey</code>, <code>RowKey</code>, and <code>Timestamp</code> are + * automatically requested from the table service whether specified in the {@link TableQuery} or not. + * + * @param columns + * An array of <code>String</code> objects containing the property names of the table entity properties + * to return when the query is executed. + */ + public void setColumns(final String[] columns) { + this.columns = columns; + } + + /** + * Sets the filter expression to use in the table query. A filter expression is optional; by default a table query + * will return all entities in the table. + * <p> + * Filter expressions for use with the {@link #setFilterString} method can be created using fluent syntax with the + * overloaded {@link #generateFilterCondition} methods and {@link #combineFilters} method, using the comparison + * operators defined in {@link QueryComparisons} and the logical operators defined in {@link Operators}. Note that + * the first operand in a filter comparison must be a property name, and the second operand must evaluate to a + * constant. The PartitionKey and RowKey property values are <code>String</code> types for comparison purposes. For + * example, to query all entities with a PartitionKey value of "AccessLogs" on table query <code>myQuery</code>: + * <p> + * <code>    myQuery.setFilterString("PartitionKey eq 'AccessLogs'");</code> + * <p> + * The values that may be used in table queries are explained in more detail in the MSDN topic + * + * <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a>, + * but note that the space characters within values do not need to be URL-encoded, as this will be done when the + * query is executed. + * <p> + * Note that no more than 15 discrete comparisons are permitted within a filter string. + * + * @param filterString + * A <code>String</code> containing the filter expression to use in the query. + */ + public void setFilterString(final String filterString) { + Utility.assertNotNullOrEmpty("filterString", filterString); + this.filterString = filterString; + } + + /** + * Sets the name of the source table for the table query. A table query must have a source table to be executed. + * + * @param sourceTableName + * A <code>String</code> containing the name of the source table to use in the query. + */ + public void setSourceTableName(final String sourceTableName) { + Utility.assertNotNullOrEmpty("tableName", sourceTableName); + this.sourceTableName = sourceTableName; + } + + /** + * Sets the upper bound for the number of entities the query returns. If this value is not specified in a table + * query, by default a maximum of 1,000 entries will be returned. + * <p> + * If the value specified for the <code>takeCount</code> parameter is greater than 1,000, the query will throw a + * {@link StorageException} when executed. + * + * @param takeCount + * The maximum number of entities for the table query to return. + */ + public void setTakeCount(final Integer takeCount) { + if (takeCount != null && takeCount <= 0) { + throw new IllegalArgumentException("Take count must be positive and greater than 0."); + } + + this.takeCount = takeCount; + } + + /** + * Defines the upper bound for the number of entities the query returns. If this value is not specified in a table + * query, by default a maximum of 1,000 entries will be returned. + * <p> + * If the value specified for the <code>take</code> parameter is greater than 1,000, the query will throw a + * {@link StorageException} when executed. + * + * @param take + * The maximum number of entities for the table query to return. + * + * @return + * A reference to the {@link TableQuery} instance with the number of entities to return set. + */ + public TableQuery<T> take(final Integer take) { + if (take != null) { + this.setTakeCount(take); + } + return this; + } + + /** + * Defines a filter expression for the table query. Only entities that satisfy the specified filter expression will + * be returned by the query. Setting a filter expression is optional; by default, all entities in the table are + * returned if no filter expression is specified in the table query. + * <p> + * Filter expressions for use with the {@link #where} method can be created using fluent syntax with the overloaded + * {@link #generateFilterCondition} methods and {@link #combineFilters} method, using the comparison operators + * defined in {@link QueryComparisons} and the logical operators defined in {@link Operators}. Note that the first + * operand in a filter comparison must be a property name, and the second operand must evaluate to a constant. The + * PartitionKey and RowKey property values are <code>String</code> types for comparison purposes. For example, to + * query all entities with a PartitionKey value of "AccessLogs" on table query <code>myQuery</code>: + * <p> + * <code>    myQuery = myQuery.where("PartitionKey eq 'AccessLogs'");</code> + * <p> + * The values that may be used in table queries are explained in more detail in the MSDN topic + * + * <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a>, + * but note that the space characters within values do not need to be URL-encoded, as this will be done when the + * query is executed. + * <p> + * Note that no more than 15 discrete comparisons are permitted within a filter string. + * + * @param filter + * A <code>String</code> containing the filter expression to apply to the table query. + * @return + * A reference to the {@link TableQuery} instance with the filter on entities to return set. + */ + public TableQuery<T> where(final String filter) { + this.setFilterString(filter); + return this; + } + + /** + * Reserved for internal use. Creates a {@link UriQueryBuilder} object representing the table query. + * + * @return + * A {@link UriQueryBuilder} object representing the table query. + * @throws StorageException + * if an error occurs in adding or encoding the query parameters. + */ + protected UriQueryBuilder generateQueryBuilder() throws StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + if (!Utility.isNullOrEmpty(this.filterString)) { + builder.add(TableConstants.FILTER, this.filterString); + } + + if (this.takeCount != null) { + builder.add(TableConstants.TOP, this.takeCount.toString()); + } + + if (this.columns != null && this.columns.length > 0) { + final StringBuilder colBuilder = new StringBuilder(); + + boolean foundRk = false; + boolean foundPk = false; + boolean roundTs = false; + + for (int m = 0; m < this.columns.length; m++) { + if (TableConstants.ROW_KEY.equals(this.columns[m])) { + foundRk = true; + } + else if (TableConstants.PARTITION_KEY.equals(this.columns[m])) { + foundPk = true; + } + else if (TableConstants.TIMESTAMP.equals(this.columns[m])) { + roundTs = true; + } + + colBuilder.append(this.columns[m]); + if (m < this.columns.length - 1) { + colBuilder.append(","); + } + } + + if (!foundPk) { + colBuilder.append(","); + colBuilder.append(TableConstants.PARTITION_KEY); + } + + if (!foundRk) { + colBuilder.append(","); + colBuilder.append(TableConstants.ROW_KEY); + } + + if (!roundTs) { + colBuilder.append(","); + colBuilder.append(TableConstants.TIMESTAMP); + } + + builder.add(TableConstants.SELECT, colBuilder.toString()); + } + + return builder; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java new file mode 100644 index 0000000000000..4642bec1b298a --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java @@ -0,0 +1,432 @@ +/** + * Copyright 2011 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.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ResultContinuation; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseRequest; + +/** + * Reserved for internal use. A class used to generate requests for Table objects. + */ +final class TableRequest { + /** + * Reserved for internal use. Adds continuation token values to the specified query builder, if set. + * + * @param builder + * The {@link UriQueryBuilder} object to apply the continuation token properties to. + * @param continuationToken + * The {@link ResultContinuation} object containing the continuation token values to apply to the query + * builder. Specify <code>null</code> if no continuation token values are set. + * + * @throws StorageException + * if an error occurs in accessing the query builder or continuation token. + */ + protected static void applyContinuationToQueryBuilder(final UriQueryBuilder builder, + final ResultContinuation continuationToken) throws StorageException { + if (continuationToken != null) { + if (continuationToken.getNextPartitionKey() != null) { + builder.add(TableConstants.TABLE_SERVICE_NEXT_PARTITION_KEY, continuationToken.getNextPartitionKey()); + } + + if (continuationToken.getNextRowKey() != null) { + builder.add(TableConstants.TABLE_SERVICE_NEXT_ROW_KEY, continuationToken.getNextRowKey()); + } + + if (continuationToken.getNextTableName() != null) { + builder.add(TableConstants.TABLE_SERVICE_NEXT_TABLE_NAME, continuationToken.getNextTableName()); + } + } + } + + /** + * Reserved for internal use. Constructs an <code>HttpURLConnection</code> to perform a table batch operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param batchID + * The <code>String</code> containing the batch identifier. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param tableOptions + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. This parameter is unused. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection batch(final URI rootUri, final int timeoutInMs, final String batchID, + final UriQueryBuilder queryBuilder, final TableRequestOptions tableOptions, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + final URI queryUri = PathUtility.appendPathToUri(rootUri, "$batch"); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(queryUri, timeoutInMs, queryBuilder, + opContext); + // Note : accept behavior, java by default sends Accept behavior + // as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, TableConstants.HeaderConstants.ACCEPT_TYPE); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, "UTF8"); + retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION, + TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE); + + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + String.format(TableConstants.HeaderConstants.MULTIPART_MIXED_FORMAT, batchID)); + + retConnection.setRequestMethod("POST"); + retConnection.setDoOutput(true); + return retConnection; + } + + /** + * Reserved for internal use. Constructs the core <code>HttpURLConnection</code> to perform an operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * The identity of the entity, to pass in the Service Managment REST operation URI as + * <code><em>tableName</em>(<em>identity</em>)</code>. If <code>null</code>, only the <em>tableName</em> + * value will be passed. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The <code>UriQueryBuilder</code> for the request. + * @param requestMethod + * The HTTP request method to set. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. This parameter is unused. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection coreCreate(final URI rootUri, final String tableName, final String eTag, + final String identity, final int timeoutInMs, final UriQueryBuilder queryBuilder, + final String requestMethod, final TableRequestOptions tableOptions, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + + URI queryUri = null; + + // Do point query / delete etc. + if (!Utility.isNullOrEmpty(identity)) { + queryUri = PathUtility.appendPathToUri(rootUri, tableName.concat(String.format("(%s)", identity))); + } + else { + queryUri = PathUtility.appendPathToUri(rootUri, tableName); + } + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(queryUri, timeoutInMs, queryBuilder, + opContext); + // Note : accept behavior, java by default sends Accept behavior + // as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, TableConstants.HeaderConstants.ACCEPT_TYPE); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, "UTF-8"); + retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION, + TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE); + + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + TableConstants.HeaderConstants.ATOMPUB_TYPE); + + if (!Utility.isNullOrEmpty(eTag)) { + retConnection.setRequestProperty(Constants.HeaderConstants.IF_MATCH, eTag); + } + + retConnection.setRequestMethod(requestMethod); + return retConnection; + } + + /** + * Reserved for internal use. Constructs an <code>HttpURLConnection</code> to perform a delete operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * The identity of the entity. The resulting request will be formatted as /tableName(identity) if not + * null or empty. + * @param eTag + * The etag of the entity. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection delete(final URI rootUri, final String tableName, final String identity, + final String eTag, final int timeoutInMs, final UriQueryBuilder queryBuilder, + final TableRequestOptions tableOptions, final OperationContext opContext) throws IOException, + URISyntaxException, StorageException { + + return coreCreate(rootUri, tableName, eTag, identity, timeoutInMs, queryBuilder, "DELETE", tableOptions, + opContext); + } + + /** + * Reserved for internal use. Constructs an <code>HttpURLConnection</code> to perform an insert operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * The identity of the entity. The resulting request will be formatted as /tableName(identity) if not + * null or empty. + * @param eTag + * The etag of the entity, can be null for straight inserts. + * @param updateType + * The {@link TableUpdateType} type of update to be performed. Specify <code>null</code> for straight + * inserts. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection insert(final URI rootUri, final String tableName, final String identity, + final String eTag, final TableUpdateType updateType, final int timeoutInMs, + final UriQueryBuilder queryBuilder, final TableRequestOptions tableOptions, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + HttpURLConnection retConnection = null; + + if (updateType == null) { + retConnection = coreCreate(rootUri, tableName, eTag, null/* identity */, timeoutInMs, queryBuilder, + "POST", tableOptions, opContext); + } + else if (updateType == TableUpdateType.MERGE) { + retConnection = coreCreate(rootUri, tableName, null/* ETAG */, identity, timeoutInMs, queryBuilder, + "POST", tableOptions, opContext); + + retConnection.setRequestProperty("X-HTTP-Method", "MERGE"); + } + else if (updateType == TableUpdateType.REPLACE) { + retConnection = coreCreate(rootUri, tableName, null/* ETAG */, identity, timeoutInMs, queryBuilder, "PUT", + tableOptions, opContext); + } + + retConnection.setDoOutput(true); + + return retConnection; + } + + /** + * Reserved for internal use. Constructs an HttpURLConnection to perform a merge operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * The identity of the entity. The resulting request will be formatted as /tableName(identity) if not + * null or empty. + * @param eTag + * The etag of the entity. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection merge(final URI rootUri, final String tableName, final String identity, + final String eTag, final int timeoutInMs, final UriQueryBuilder queryBuilder, + final TableRequestOptions tableOptions, final OperationContext opContext) throws IOException, + URISyntaxException, StorageException { + final HttpURLConnection retConnection = coreCreate(rootUri, tableName, eTag, identity, timeoutInMs, + queryBuilder, "POST", tableOptions, opContext); + retConnection.setRequestProperty("X-HTTP-Method", "MERGE"); + retConnection.setDoOutput(true); + return retConnection; + } + + /** + * Reserved for internal use. Constructs an HttpURLConnection to perform a single entity query operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * The identity of the entity. The resulting request will be formatted as /tableName(identity) if not + * null or empty. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection query(final URI rootUri, final String tableName, final String identity, + final int timeoutInMs, UriQueryBuilder queryBuilder, final ResultContinuation continuationToken, + final TableRequestOptions tableOptions, final OperationContext opContext) throws IOException, + URISyntaxException, StorageException { + if (queryBuilder == null) { + queryBuilder = new UriQueryBuilder(); + } + + applyContinuationToQueryBuilder(queryBuilder, continuationToken); + final HttpURLConnection retConnection = coreCreate(rootUri, tableName, null, identity, timeoutInMs, + queryBuilder, "GET", tableOptions, opContext); + + return retConnection; + } + + /** + * Reserved for internal use. Constructs an HttpURLConnection to perform an update operation. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param identity + * A <code>String</code> representing the identity of the entity. The resulting request will be formatted + * using <em>/tableName(identity)</em> if identity is not >code>null</code> or empty. + * @param eTag + * The etag of the entity. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param queryBuilder + * The {@link UriQueryBuilder} for the operation. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return + * An <code>HttpURLConnection</code> to use to perform the operation. + * + * @throws IOException + * if there is an error opening the connection. + * @throws URISyntaxException + * if the resource URI is invalid. + * @throws StorageException + * if a storage service error occurred during the operation. + */ + protected static HttpURLConnection update(final URI rootUri, final String tableName, final String identity, + final String eTag, final int timeoutInMs, final UriQueryBuilder queryBuilder, + final TableRequestOptions tableOptions, final OperationContext opContext) throws IOException, + URISyntaxException, StorageException { + final HttpURLConnection retConnection = coreCreate(rootUri, tableName, eTag, identity, timeoutInMs, + queryBuilder, "PUT", tableOptions, opContext); + + retConnection.setDoOutput(true); + return retConnection; + } + + /** + * Private Default Constructor. + */ + private TableRequest() { + // No op + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequestOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequestOptions.java new file mode 100644 index 0000000000000..1a4d210526f20 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequestOptions.java @@ -0,0 +1,35 @@ +/** + * Copyright 2011 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 com.microsoft.windowsazure.services.core.storage.RequestOptions; + +/** + * Represents a set of timeout and retry policy options that may be specified for a table operation request. + */ +public class TableRequestOptions extends RequestOptions { + /** + * Reserved for internal use. Initializes the timeout and retry policy for this <code>TableRequestOptions</code> + * instance, if they are currently <code>null</code>, using the values specified in the {@link CloudTableClient} + * parameter. + * + * @param client + * The {@link CloudTableClient} client object to copy the timeout and retry policy from. + */ + protected void applyDefaults(final CloudTableClient client) { + super.applyBaseDefaults(client); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResponse.java new file mode 100644 index 0000000000000..848e7b2afc0bc --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResponse.java @@ -0,0 +1,75 @@ +/** + * Copyright 2011 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.net.HttpURLConnection; + +import com.microsoft.windowsazure.services.core.storage.ResultContinuation; +import com.microsoft.windowsazure.services.core.storage.ResultContinuationType; + +/** + * Reserved for internal use. A class used to help parse responses from the Table service. + */ +class TableResponse { + /** + * Reserved for internal use. A static factory method that constructs a {@link ResultContinuation} instance from the + * continuation token information in a table operation response, if any. + * + * @param queryRequest + * The <code>java.net.HttpURLConnection<code> request response to parse for continuation token + * information. + * + * @return + * A {@link ResultContinuation} instance from continuation token information in the response, or + * <code>null</code> if none is found. + */ + protected static ResultContinuation getTableContinuationFromResponse(final HttpURLConnection queryRequest) { + final ResultContinuation retVal = new ResultContinuation(); + retVal.setContinuationType(ResultContinuationType.TABLE); + + boolean foundToken = false; + + String tString = queryRequest.getHeaderField(TableConstants.TABLE_SERVICE_PREFIX_FOR_TABLE_CONTINUATION + .concat(TableConstants.TABLE_SERVICE_NEXT_PARTITION_KEY)); + if (tString != null) { + retVal.setNextPartitionKey(tString); + foundToken = true; + } + + tString = queryRequest.getHeaderField(TableConstants.TABLE_SERVICE_PREFIX_FOR_TABLE_CONTINUATION + .concat(TableConstants.TABLE_SERVICE_NEXT_ROW_KEY)); + if (tString != null) { + retVal.setNextRowKey(tString); + foundToken = true; + } + + tString = queryRequest.getHeaderField(TableConstants.TABLE_SERVICE_PREFIX_FOR_TABLE_CONTINUATION + .concat(TableConstants.TABLE_SERVICE_NEXT_MARKER)); + if (tString != null) { + retVal.setNextMarker(tString); + foundToken = true; + } + + tString = queryRequest.getHeaderField(TableConstants.TABLE_SERVICE_PREFIX_FOR_TABLE_CONTINUATION + .concat(TableConstants.TABLE_SERVICE_NEXT_TABLE_NAME)); + if (tString != null) { + retVal.setNextTableName(tString); + foundToken = true; + } + + return foundToken ? retVal : null; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResult.java new file mode 100644 index 0000000000000..95e0621b63ed9 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableResult.java @@ -0,0 +1,179 @@ +/** + * Copyright 2011 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.util.HashMap; + +/** + * A class which represents the result of a table operation. The {@link TableResult} class encapsulates the HTTP + * response + * and any table entity results returned by the Storage Service REST API operation called for a particular + * {@link TableOperation}. + * + */ +public class TableResult { + private Object result; + + private int httpStatusCode = -1; + + private String id; + + private String etag; + + private HashMap<String, EntityProperty> properties; + + /** + * Initializes an empty {@link TableResult} instance. + */ + public TableResult() { + // empty ctor + } + + /** + * Initializes a {@link TableResult} instance with the specified HTTP status code. + * + * @param httpStatusCode + * The HTTP status code for the table operation returned by the server. + */ + public TableResult(final int httpStatusCode) { + this.httpStatusCode = httpStatusCode; + } + + /** + * Gets the Etag returned with the table operation results. The server will return the same Etag value for a + * table, entity, or entity group returned by an operation as long as it is unchanged on the server. + * + * @return + * A <code>String</code> containing the Etag returned by the server with the table operation results. + */ + public String getEtag() { + return this.etag; + } + + /** + * Gets the HTTP status code returned by a table operation request. + * + * @return + * The HTTP status code for the table operation returned by the server. + */ + public int getHttpStatusCode() { + return this.httpStatusCode; + } + + /** + * Gets the AtomPub Entry Request ID value for the result returned by a table operation request. + * + * @return + * The Entry Request ID for the table operation result. + */ + public String getId() { + return this.id; + } + + /** + * Gets the map of properties for a table entity returned by the table operation. + * + * @return + * A <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} data + * typed values representing the properties of a table entity. + */ + public HashMap<String, EntityProperty> getProperties() { + return this.properties; + } + + /** + * Gets the result returned by the table operation as an Object. + * + * @return + * The result returned by the table operation as an <code>Object</code>. + */ + public Object getResult() { + return this.result; + } + + /** + * Gets the result returned by the table operation as an instance of the specified type. + * + * @return + * The result returned by the table operation as an instance of type <code>T</code>. + */ + @SuppressWarnings("unchecked") + public <T> T getResultAsType() { + return (T) this.getResult(); + } + + /** + * Reserved for internal use. Sets the Etag associated with the table operation results. + * + * @param etag + * A <code>String</code> containing an Etag to associate with the table operation results. + */ + protected void setEtag(final String etag) { + this.etag = etag; + } + + /** + * Reserved for internal use. Sets the HTTP status code associated with the table operation results. + * + * @param httpStatusCode + * The HTTP status code value to associate with the table operation results. + */ + protected void setHttpStatusCode(final int httpStatusCode) { + this.httpStatusCode = httpStatusCode; + } + + /** + * Reserved for internal use. Sets the AtomPub Entry Request ID associated with the table operation result. + * + * @param id + * A <code>String</code> containing the request ID to associate with the table operation result. + */ + protected void setId(final String id) { + this.id = id; + } + + /** + * Reserved for internal use. Sets the map of properties for a table entity to associate with the table operation. + * + * @param properties + * A <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} data + * typed values representing the properties of a table entity to associate with the table operation. + */ + protected void setProperties(final HashMap<String, EntityProperty> properties) { + this.properties = properties; + } + + /** + * Reserved for internal use. Sets a result Object instance to associate with the table operation. + * + * @param result + * An instance of a result <code>Object</code> to associate with the table operation. + */ + protected void setResult(final Object result) { + this.result = result; + } + + /** + * Reserved for internal use. Sets the result to associate with the table operation as a {@link TableEntity}. + * + * @param ent + * An instance of an object implementing {@link TableEntity} to associate with the table operation. + */ + protected void updateResultObject(final TableEntity ent) { + this.result = ent; + ent.setEtag(this.etag); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java new file mode 100644 index 0000000000000..d958bb328444b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java @@ -0,0 +1,414 @@ +/** + * Copyright 2011 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.lang.reflect.InvocationTargetException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; + +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; + +/** + * The {@link TableServiceEntity} class represents the base object type for a table entity in the Storage service. + * {@link TableServiceEntity} provides a base implementation for the {@link TableEntity} interface that provides + * <code>readEntity</code> and <code>writeEntity</code> methods that by default serialize and deserialize all properties + * via reflection. A table entity class may extend this class and override the <code>readEntity</code> and + * <code>writeEntity</code> methods to provide customized or more performant serialization logic. + * <p> + * The use of reflection allows subclasses of {@link TableServiceEntity} to be serialized and deserialized without + * having to implement the serialization code themselves. When both a getter method and setter method are found for a + * given property name and data type, then the appropriate method is invoked automatically to serialize or deserialize + * the data. To take advantage of the automatic serialization code, your table entity classes should provide getter and + * setter methods for each property in the corresponding table entity in Windows Azure table storage. The reflection + * code looks for getter and setter methods in pairs of the form + * <p> + * <code>public <em>type</em> get<em>PropertyName</em>() { ... }</code> + * <p> + * and + * <p> + * <code>public void set<em>PropertyName</em>(<em>type</em> parameter) { ... }</code> + * <p> + * where <em>PropertyName</em> is a property name for the table entity, and <em>type</em> is a Java type compatible with + * the EDM data type of the property. See the table below for a map of property types to their Java equivalents. The + * {@link StoreAs} annotation may be applied with a <code>name</code> attribute to specify a property name for + * reflection on getter and setter methods that do not follow the property name convention. Method names and the + * <code>name</code> attribute of {@link StoreAs} annotations are case sensitive for matching property names with + * reflection. Use the {@link Ignore} annotation to prevent methods from being used by reflection for automatic + * serialization and deserialization. Note that the names "PartitionKey", "RowKey", "Timestamp", and "Etag" are reserved + * and will be ignored if set with the {@link StoreAs} annotation in a subclass. + * <p> + * The following table shows the supported property data types in Windows Azure storage and the corresponding Java types + * when deserialized. + * <TABLE BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0"> + * <TR BGCOLOR="#EEEEFF" CLASS="TableSubHeadingColor"> + * <th>Storage Type</th> + * <th>EdmType Value</th> + * <th>Java Type</th> + * <th>Description</th> + * </tr> + * <tr> + * <td><strong>Edm.Binary</strong></td> + * <td>{@link EdmType#BINARY}</td> + * <td><code>byte[], Byte[]</code></td> + * <td>An array of bytes up to 64 KB in size.</td> + * </tr> + * <tr> + * <td><strong>Edm.Boolean</strong></td> + * <td>{@link EdmType#BOOLEAN}</td> + * <td><code>boolean, Boolean</code></td> + * <td>A Boolean value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Byte</strong></td> + * <td>{@link EdmType#BYTE}</td> + * <td><code>boolean, Boolean</code></td> + * <td>A Boolean value.</td> + * </tr> + * <tr> + * <td><strong>Edm.DateTime</strong></td> + * <td>{@link EdmType#DATE_TIME}</td> + * <td><code>Date</code></td> + * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, + * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> + * </tr> + * <tr> + * <td><strong>Edm.Double</strong></td> + * <td>{@link EdmType#DOUBLE}</td> + * <td><code>double, Double</code></td> + * <td>A 64-bit double-precision floating point value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Guid</strong></td> + * <td>{@link EdmType#GUID}</td> + * <td><code>UUID</code></td> + * <td>A 128-bit globally unique identifier.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int32</strong></td> + * <td>{@link EdmType#INT32}</td> + * <td><code>int, Integer</code></td> + * <td>A 32-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int64</strong></td> + * <td>{@link EdmType#INT64}</td> + * <td><code>long, Long</code></td> + * <td>A 64-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.String</strong></td> + * <td>{@link EdmType#STRING}</td> + * <td><code>String</code></td> + * <td>A UTF-16-encoded value. String values may be up to 64 KB in size.</td> + * </tr> + * </table> + * <p> + * See the MSDN topic <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179338.aspx">Understanding the + * Table Service Data Model</a> for an overview of tables, entities, and properties as used in the Windows Azure Storage + * service. + * <p> + * For an overview of the available EDM primitive data types and names, see the + * + * <a href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of + * the <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>. + * <p> + * + * @see EdmType + */ +public class TableServiceEntity implements TableEntity { + /** + * Deserializes the table entity property map into the specified object instance using reflection. + * <p> + * This static method takes an object instance that represents a table entity type and uses reflection on its class + * type to find methods to deserialize the data from the property map into the instance. + * <p> + * Each property name and data type in the properties map is compared with the methods in the class type for a pair + * of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with + * names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set + * with the property name. The methods must have return types or parameter data types that match the data type of + * the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied into the instance + * object by invoking the setter method on the instance. Properties that do not match a method pair by name and data + * type are not copied. + * + * @param instance + * A reference to an instance of a class implementing {@link TableEntity} to deserialize the table entity + * data into. + * @param properties + * A map of <code>String</code> property names to {@link EntityProperty} objects containing typed data + * values to deserialize into the instance parameter object. + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. + * + * @throws IllegalArgumentException + * if the table entity response received is invalid or improperly formatted. + * @throws IllegalAccessException + * if the table entity threw an exception during deserialization. + * @throws InvocationTargetException + * if a method invoked on the instance parameter threw an exception during deserialization. + */ + public static void readEntityWithReflection(final Object instance, + final HashMap<String, EntityProperty> properties, final OperationContext opContext) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass()); + + for (final Entry<String, EntityProperty> p : properties.entrySet()) { + if (props.containsKey(p.getKey())) { + // TODO add logging + // System.out.println("Consuming " + p.getKey() + ":" + p.getValue().getValueAsString()); + props.get(p.getKey()).consumeTableProperty(p.getValue(), instance); + } + } + } + + /** + * Serializes the property data from a table entity instance into a property map using reflection. + * <p> + * This static method takes an object instance that represents a table entity type and uses reflection on its class + * type to find methods to serialize the data from the instance into the property map. + * <p> + * Each property name and data type in the properties map is compared with the methods in the class type for a pair + * of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with + * names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set + * with the property name. The methods must have return types or parameter data types that match the data type of + * the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied from the instance + * object by invoking the getter method on the instance. Properties that do not have a method pair with matching + * name and data type are not copied. + * + * @param instance + * A reference to an instance of a class implementing {@link TableEntity} to serialize the table entity + * data from. + * @return + * A map of <code>String</code> property names to {@link EntityProperty} objects containing typed data + * values serialized from the instance parameter object. + * + * @throws IllegalArgumentException + * if the table entity is invalid or improperly formatted. + * @throws IllegalAccessException + * if the table entity threw an exception during serialization. + * @throws InvocationTargetException + * if a method invoked on the instance parameter threw an exception during serialization. + */ + public static HashMap<String, EntityProperty> writeEntityWithReflection(final Object instance) + throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass()); + + final HashMap<String, EntityProperty> retVal = new HashMap<String, EntityProperty>(); + for (final Entry<String, PropertyPair> p : props.entrySet()) { + retVal.put(p.getValue().effectiveName, p.getValue().generateTableProperty(instance)); + } + + return retVal; + } + + /** + * Reserved for internal use. The value of the partition key in the entity. + */ + protected String partitionKey = null; + + /** + * Reserved for internal use. The value of the row key in the entity. + */ + protected String rowKey = null; + + /** + * Reserved for internal use. The value of the Etag for the entity. + */ + protected String etag = null; + + /** + * Reserved for internal use. The value of the Timestamp in the entity. + */ + protected Date timeStamp = new Date(); + + /** + * Initializes an empty {@link TableServiceEntity} instance. + */ + public TableServiceEntity() { + // Empty ctor + } + + /** + * Gets the Etag value for the entity. This value is used to determine if the table entity has changed since it was + * last read from Windows Azure storage. + * + * @return + * A <code>String</code> containing the Etag for the entity. + */ + @Override + public String getEtag() { + return this.etag; + } + + /** + * Gets the PartitionKey value for the entity. + * + * @return + * A <code>String</code> containing the PartitionKey value for the entity. + */ + @Override + public String getPartitionKey() { + return this.partitionKey; + } + + /** + * Gets the RowKey value for the entity. + * + * @return + * A <code>String</code> containing the RowKey value for the entity. + */ + @Override + public String getRowKey() { + return this.rowKey; + } + + /** + * Gets the Timestamp value for the entity. + * + * @return + * A <code>Date</code> containing the Timestamp value for the entity. + */ + @Override + public Date getTimestamp() { + return this.timeStamp; + } + + /** + * Populates this table entity instance using the map of property names to {@link EntityProperty} data typed values. + * <p> + * This method invokes {@link TableServiceEntity#readEntityWithReflection} to populate the table entity instance the + * method is called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take + * advantage of this behavior by implementing getter and setter methods for the particular properties of the table + * entity in Windows Azure storage the class represents. + * <p> + * Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code. + * + * @param properties + * The <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} + * data values to deserialize and store in this table entity instance. + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * @throws StorageException + * if an error occurs during the deserialization. + */ + @Override + public void readEntity(final HashMap<String, EntityProperty> properties, final OperationContext opContext) + throws StorageException { + try { + readEntityWithReflection(this, properties, opContext); + } + catch (IllegalArgumentException e) { + throw new StorageException(StorageErrorCodeStrings.INVALID_XML_DOCUMENT, + "The response received is invalid or improperly formatted.", + Constants.HeaderConstants.HTTP_UNUSED_306, null, e); + } + catch (IllegalAccessException e) { + throw new StorageException(StorageErrorCodeStrings.INVALID_XML_DOCUMENT, + "The entity threw an exception during deserialization", Constants.HeaderConstants.HTTP_UNUSED_306, + null, e); + } + catch (InvocationTargetException e) { + throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR, + "The entity threw an exception during deserialization", Constants.HeaderConstants.HTTP_UNUSED_306, + null, e); + } + } + + /** + * Sets the Etag value for the entity. This value is used to determine if the table entity has changed since it was + * last read from Windows Azure storage. + * + * @param etag + * A <code>String</code> containing the Etag for the entity. + */ + @Override + public void setEtag(final String etag) { + this.etag = etag; + } + + /** + * Sets the PartitionKey value for the entity. + * + * @param partitionKey + * A <code>String</code> containing the PartitionKey value for the entity. + */ + @Override + public void setPartitionKey(final String partitionKey) { + this.partitionKey = partitionKey; + } + + /** + * Sets the RowKey value for the entity. + * + * @param rowKey + * A <code>String</code> containing the RowKey value for the entity. + */ + @Override + public void setRowKey(final String rowKey) { + this.rowKey = rowKey; + } + + /** + * Sets the Timestamp value for the entity. + * + * @param timeStamp + * A <code>Date</code> containing the Timestamp value for the entity. + */ + @Override + public void setTimestamp(final Date timeStamp) { + this.timeStamp = timeStamp; + } + + /** + * Returns a map of property names to {@link EntityProperty} data typed values created by serializing this table + * entity instance. + * <p> + * This method invokes {@link #writeEntityWithReflection} to serialize the table entity instance the method is + * called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take advantage of + * this behavior by implementing getter and setter methods for the particular properties of the table entity in + * Windows Azure storage the class represents. Note that the property names "PartitionKey", "RowKey", and + * "Timestamp" are reserved and will be ignored if set on other methods with the {@link StoreAs} annotation. + * <p> + * Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code. + * + * @param opContext + * An {@link OperationContext} object used to track the execution of the operation. + * @return + * A <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} data + * typed values representing the properties serialized from this table entity instance. + * @throws StorageException + * if an error occurs during the serialization. + */ + @Override + public HashMap<String, EntityProperty> writeEntity(final OperationContext opContext) throws StorageException { + try { + return writeEntityWithReflection(this); + } + catch (final IllegalAccessException e) { + throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR, + "An attempt was made to access an inaccessible member of the entity during serialization.", + Constants.HeaderConstants.HTTP_UNUSED_306, null, e); + } + catch (final InvocationTargetException e) { + throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR, + "The entity threw an exception during serialization", Constants.HeaderConstants.HTTP_UNUSED_306, + null, e); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceException.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceException.java new file mode 100644 index 0000000000000..04f065a4407df --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceException.java @@ -0,0 +1,172 @@ +/** + * Copyright 2011 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.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; + +import javax.xml.stream.XMLStreamException; + +import com.microsoft.windowsazure.services.core.storage.RequestResult; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.StorageExtendedErrorInformation; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageErrorResponse; + +/** + * An exception that results when a table storage service operation fails to complete successfully. + */ +public class TableServiceException extends StorageException { + + private static final long serialVersionUID = 6037366449663934891L; + + /** + * Reserved for internal use. A static factory method to create a {@link TableServiceException} instance using + * the specified parameters. + * + * @param retryable + * A flag indicating the table operation can be retried. + * @param res + * A {@link RequestResult} containing the result of the table storage service operation. + * @param op + * The {@link TableOperation} representing the table operation that caused the exception. + * @param inStream + * The <code>java.io.InputStream</code> of the error response from the table operation request. + * @return + * A {@link TableServiceException} instance initialized with values from the input parameters. + * @throws IOException + * if an IO error occurs. + */ + protected static TableServiceException generateTableServiceException(boolean retryable, RequestResult res, + TableOperation op, InputStream inStream) throws IOException { + try { + TableServiceException retryableException = new TableServiceException(res.getStatusCode(), + res.getStatusMessage(), op, new InputStreamReader(inStream)); + retryableException.retryable = retryable; + + return retryableException; + } + finally { + inStream.close(); + } + } + + private TableOperation operation; + + /** + * Reserved for internal use. This flag indicates whether the operation that threw the exception can be retried. + */ + protected boolean retryable = false; + + /** + * Constructs a <code>TableServiceException</code> instance using the specified error code, message, status code, + * extended error information and inner exception. + * + * @param errorCode + * A <code>String</code> that represents the error code returned by the table operation. + * @param message + * A <code>String</code> that represents the error message returned by the table operation. + * @param statusCode + * The HTTP status code returned by the table operation. + * @param extendedErrorInfo + * A {@link StorageExtendedErrorInformation} object that represents the extended error information + * returned by the table operation. + * @param innerException + * An <code>Exception</code> object that represents a reference to the initial exception, if one exists. + */ + public TableServiceException(final String errorCode, final String message, final int statusCode, + final StorageExtendedErrorInformation extendedErrorInfo, final Exception innerException) { + super(errorCode, message, statusCode, extendedErrorInfo, innerException); + } + + /** + * Reserved for internal use. Constructs a <code>TableServiceException</code> instance using the specified HTTP + * status code, message, operation, and stream reader. + * + * @param httpStatusCode + * The <code>int</code> HTTP Status Code value returned by the table operation that caused the exception. + * @param message + * A <code>String</code> description of the error that caused the exception. + * @param operation + * The {@link TableOperation} object representing the table operation that was in progress when the + * exception occurred. + * @param reader + * The <code>Java.IO.Stream</code> derived stream reader for the HTTP request results returned by the + * table operation, if any. + */ + protected TableServiceException(final int httpStatusCode, final String message, final TableOperation operation, + final Reader reader) { + super(null, message, httpStatusCode, null, null); + this.operation = operation; + + if (reader != null) { + try { + final StorageErrorResponse error = new StorageErrorResponse(reader); + this.extendedErrorInformation = error.getExtendedErrorInformation(); + this.errorCode = this.extendedErrorInformation.getErrorCode(); + } + catch (XMLStreamException e) { + // no-op, if error parsing fails, just throw first exception. + } + } + } + + /** + * Gets the table operation that caused the <code>TableServiceException</code> to be thrown. + * + * @return + * The {@link TableOperation} object representing the table operation that caused this + * {@link TableServiceException} to be thrown. + */ + public TableOperation getOperation() { + return this.operation; + } + + /** + * Reserved for internal use. Gets a flag indicating the table operation can be retried. + * + * @return + * The <code>boolean</code> flag indicating whether the table operation that caused the exception can be + * retried. + */ + public boolean isRetryable() { + return this.retryable; + } + + /** + * Reserved for internal use. Sets the table operation that caused the <code>TableServiceException</code> to be + * thrown. + * + * @param operation + * The {@link TableOperation} object representing the table operation that caused this + * {@link TableServiceException} to be thrown. + */ + protected void setOperation(final TableOperation operation) { + this.operation = operation; + } + + /** + * Reserved for internal use. Sets a flag indicating the table operation can be retried. + * + * @param retryable + * The <code>boolean</code> flag to set indicating whether the table operation that caused the exception + * can be retried. + */ + protected void setRetryable(boolean retryable) { + this.retryable = retryable; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableUpdateType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableUpdateType.java new file mode 100644 index 0000000000000..3fbf519729c44 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableUpdateType.java @@ -0,0 +1,31 @@ +/** + * Copyright 2011 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; + +/** + * Reserved for internal use. An enum that represents the type of update a given upsert operation will perform. + */ +enum TableUpdateType { + /** + * The table operation updates an existing entity. + */ + MERGE, + + /** + * The table operation replaces an existing entity. + */ + REPLACE; +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableBatchOperationTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableBatchOperationTests.java new file mode 100644 index 0000000000000..671ac7c308a75 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableBatchOperationTests.java @@ -0,0 +1,762 @@ +/** + * Copyright 2011 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 static org.junit.Assert.*; + +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.StorageException; + +public class TableBatchOperationTests extends TableTestBase { + @Test + public void batchDelete() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + + // insert entity + class1 ref = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + batch.delete(ref); + + ArrayList<TableResult> delResults = tClient.execute(testSuiteTableName, batch); + for (TableResult r : delResults) { + Assert.assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + try { + tClient.execute(testSuiteTableName, batch); + fail(); + } + catch (StorageException ex) { + Assert.assertEquals(ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + } + + @Test + public void batchDeleteFail() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + + // Insert entity to delete + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + class1 updatedEntity = generateRandomEnitity("jxscl_odata"); + updatedEntity.setPartitionKey(baseEntity.getPartitionKey()); + updatedEntity.setRowKey(baseEntity.getRowKey()); + updatedEntity.setEtag(baseEntity.getEtag()); + tClient.execute(testSuiteTableName, TableOperation.replace(updatedEntity)); + + // add delete to fail + batch.delete(baseEntity); + + try { + @SuppressWarnings("unused") + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The update condition specified in the request was not satisfied.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied"); + } + } + + @Test + public void batchEmptyQuery() throws StorageException { + // insert entity + class1 ref = generateRandomEnitity("jxscl_odata"); + + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass()); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + + Assert.assertEquals(results.size(), 1); + Assert.assertNull(results.get(0).getResult()); + Assert.assertEquals(results.get(0).getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + + @Test + public void batchInsertFail() throws StorageException { + // insert entity + class1 ref = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.insert(ref); + tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Conflict"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The specified entity already exists")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityAlreadyExists"); + } + } + + @Test + public void batchLockToPartitionKey() throws StorageException { + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.insert(generateRandomEnitity("jxscl_odata")); + batch.insert(generateRandomEnitity("jxscl_odata2")); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "All entities in a given batch must have the same partition key."); + } + } + + @Test + public void batchMergeFail() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + addInsertBatch(batch); + + // Insert entity to merge + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + class1 updatedEntity = generateRandomEnitity("jxscl_odata"); + updatedEntity.setPartitionKey(baseEntity.getPartitionKey()); + updatedEntity.setRowKey(baseEntity.getRowKey()); + updatedEntity.setEtag(baseEntity.getEtag()); + tClient.execute(testSuiteTableName, TableOperation.replace(updatedEntity)); + + // add merge to fail + addMergeToBatch(baseEntity, batch); + + try { + @SuppressWarnings("unused") + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1); + Assert.assertTrue(errorAfterSemiColon + .startsWith("The update condition specified in the request was not satisfied.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied"); + } + } + + @Test + public void batchMultiQueryShouldThrow() throws StorageException { + class1 ref = generateRandomEnitity("jxscl_odata"); + class1 ref2 = generateRandomEnitity("jxscl_odata"); + + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass()); + batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass()); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), + "A batch transaction with a retrieve operation cannot contain any other operations."); + } + } + + @Test + public void batchAddNullShouldThrow() throws StorageException { + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.add(null); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "element"); + } + } + + @Test + public void batchRetrieveWithNullResolver() throws StorageException { + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve("foo", "blah", (EntityResolver<?>) null); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Query requires a valid class type or resolver."); + } + } + + @Test + public void batchOver100Entities() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + try { + for (int m = 0; m < 101; m++) { + batch.insert(generateRandomEnitity("jxscl_odata")); + } + + tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1); + Assert.assertTrue(errorAfterSemiColon.startsWith("One of the request inputs is not valid.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "InvalidInput"); + } + } + + @Test + public void batchQuery() throws StorageException { + // insert entity + class1 ref = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableBatchOperation batch = new TableBatchOperation(); + + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), ref.getClass()); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 1); + + Assert.assertEquals(results.get(0).getHttpStatusCode(), HttpURLConnection.HTTP_OK); + class1 retrievedRef = results.get(0).getResultAsType(); + + Assert.assertEquals(ref.getA(), retrievedRef.getA()); + Assert.assertEquals(ref.getB(), retrievedRef.getB()); + Assert.assertEquals(ref.getC(), retrievedRef.getC()); + Assert.assertTrue(Arrays.equals(ref.getD(), retrievedRef.getD())); + + tClient.execute(testSuiteTableName, TableOperation.delete(ref)); + } + + @Test + public void batchQueryAndOneMoreOperationShouldThrow() throws StorageException { + class1 ref2 = generateRandomEnitity("jxscl_odata"); + + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.insert(generateRandomEnitity("jxscl_odata")); + batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass()); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), + "A batch transaction with a retrieve operation cannot contain any other operations."); + } + + try { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref2.getPartitionKey(), ref2.getRowKey(), ref2.getClass()); + batch.insert(generateRandomEnitity("jxscl_odata")); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), + "A batch transaction with a retrieve operation cannot contain any other operations."); + } + } + + @Test + public void batchReplaceFail() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + + // Insert entity to merge + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + class1 updatedEntity = generateRandomEnitity("jxscl_odata"); + updatedEntity.setPartitionKey(baseEntity.getPartitionKey()); + updatedEntity.setRowKey(baseEntity.getRowKey()); + updatedEntity.setEtag(baseEntity.getEtag()); + tClient.execute(testSuiteTableName, TableOperation.replace(updatedEntity)); + + // add merge to fail + addReplaceToBatch(baseEntity, batch); + + try { + @SuppressWarnings("unused") + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1); + Assert.assertTrue(errorAfterSemiColon + .startsWith("The condition specified using HTTP conditional header(s) is not met.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ConditionNotMet"); + } + } + + @Test + public void batchInsertEntityOver1MB() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + class1 bigEnt = new class1(); + + bigEnt.setA("foo_A"); + bigEnt.setB("foo_B"); + bigEnt.setC("foo_C"); + // 1mb right here + bigEnt.setD(new byte[1024 * 1024]); + bigEnt.setPartitionKey("jxscl_odata"); + bigEnt.setRowKey(UUID.randomUUID().toString()); + + batch.insert(bigEnt); + + for (int m = 0; m < 3; m++) { + class1 ref = new class1(); + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + batch.insert(ref); + } + + try { + tClient.execute(testSuiteTableName, batch); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1); + Assert.assertTrue(errorAfterSemiColon.startsWith("The entity is larger than allowed by the Table Service.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityTooLarge"); + } + } + + @Test + public void batchInsertEntityWithPropertyMoreThan255chars() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + DynamicTableEntity bigEnt = new DynamicTableEntity(); + + String propName = ""; + for (int m = 0; m < 255; m++) { + propName.concat(Integer.toString(m % 9)); + } + + bigEnt.getProperties().put(propName, new EntityProperty("test")); + bigEnt.setPartitionKey("jxscl_odata"); + bigEnt.setRowKey(UUID.randomUUID().toString()); + + batch.insert(bigEnt); + + for (int m = 0; m < 3; m++) { + class1 ref = new class1(); + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + batch.insert(ref); + } + + try { + tClient.execute(testSuiteTableName, batch); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + errorAfterSemiColon = errorAfterSemiColon.substring(errorAfterSemiColon.indexOf(":") + 1); + Assert.assertTrue(errorAfterSemiColon.startsWith("One of the request inputs is not valid.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "InvalidInput"); + } + } + + @Test + public void batchSizeOver4mb() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + byte[] datArr = new byte[1024 * 128]; + Random rand = new Random(); + rand.nextBytes(datArr); + + // Each entity is approx 128kb, meaning ~32 entities will result in a request over 4mb. + try { + for (int m = 0; m < 32; m++) { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(datArr); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + batch.insert(ref); + } + + tClient.execute(testSuiteTableName, batch); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + String errorAfterSemiColon = ex.getExtendedErrorInformation().getErrorMessage(); + Assert.assertTrue(errorAfterSemiColon + .startsWith("The content length for the requested operation has exceeded the limit.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ContentLengthExceeded"); + } + } + + @Test + public void batchWithAllOperations() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + // insert + addInsertBatch(batch); + + { + // insert entity to delete + class1 delRef = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(delRef)); + batch.delete(delRef); + } + + { + // Insert entity to replace + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addReplaceToBatch(baseEntity, batch); + } + + { + // Insert entity to insert or replace + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addInsertOrReplaceToBatch(baseEntity, batch); + } + + { + // Insert entity to merge + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addMergeToBatch(baseEntity, batch); + } + + { + // Insert entity to merge, no pre-esisting entity + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addInsertOrMergeToBatch(baseEntity, batch); + } + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 6); + + Iterator<TableResult> iter = results.iterator(); + + // insert + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // replace + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // insert or replace + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // merge + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // insert or merge + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + } + + @Test + public void batchInsert() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + + // Add 3 inserts + for (int m = 0; m < 3; m++) { + addInsertBatch(batch); + } + + // insert entity + class1 ref = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + batch.delete(ref); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 4); + + Iterator<TableResult> iter = results.iterator(); + + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void batchMerge() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + addInsertBatch(batch); + + // insert entity to delete + class1 delRef = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(delRef)); + batch.delete(delRef); + + // Insert entity to merge + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addMergeToBatch(baseEntity, batch); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 3); + + Iterator<TableResult> iter = results.iterator(); + + // insert + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // merge + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void batchReplace() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + addInsertBatch(batch); + + // insert entity to delete + class1 delRef = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(delRef)); + batch.delete(delRef); + + // Insert entity to replace + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addReplaceToBatch(baseEntity, batch); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 3); + + Iterator<TableResult> iter = results.iterator(); + + // insert + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // replace + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void batchInsertOrMerge() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + addInsertBatch(batch); + + // insert entity to delete + class1 delRef = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(delRef)); + batch.delete(delRef); + + // Insert entity to merge + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addInsertOrMergeToBatch(baseEntity, batch); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 3); + + Iterator<TableResult> iter = results.iterator(); + + // insert + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // merge + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void batchInsertOrReplace() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + addInsertBatch(batch); + + // insert entity to delete + class1 delRef = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(delRef)); + batch.delete(delRef); + + // Insert entity to replace + class1 baseEntity = generateRandomEnitity("jxscl_odata"); + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + addInsertOrReplaceToBatch(baseEntity, batch); + + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + Assert.assertEquals(results.size(), 3); + + Iterator<TableResult> iter = results.iterator(); + + // insert + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + + // delete + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // replace + Assert.assertEquals(iter.next().getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void emptyBatch() throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + + try { + tClient.execute(testSuiteTableName, batch); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Cannot Execute an empty batch operation"); + } + } + + @Test + public void insertBatch1() throws StorageException { + insertAndDeleteBatchWithX(1); + } + + @Test + public void insertBatch10() throws StorageException { + insertAndDeleteBatchWithX(10); + } + + @Test + public void insertBatch100() throws StorageException { + insertAndDeleteBatchWithX(100); + } + + @Test + public void upsertBatch1() throws StorageException { + upsertAndDeleteBatchWithX(1); + } + + @Test + public void upsertBatch10() throws StorageException { + upsertAndDeleteBatchWithX(10); + } + + @Test + public void upsertBatch100() throws StorageException { + upsertAndDeleteBatchWithX(100); + } + + private class1 addInsertBatch(TableBatchOperation batch) { + class1 ref = generateRandomEnitity("jxscl_odata"); + batch.insert(ref); + return ref; + } + + private class2 addInsertOrMergeToBatch(class1 baseEntity, TableBatchOperation batch) { + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + batch.insertOrMerge(secondEntity); + return secondEntity; + } + + private class2 addInsertOrReplaceToBatch(class1 baseEntity, TableBatchOperation batch) { + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + batch.insertOrReplace(secondEntity); + return secondEntity; + } + + private class2 addMergeToBatch(class1 baseEntity, TableBatchOperation batch) { + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + batch.merge(secondEntity); + return secondEntity; + } + + private class2 addReplaceToBatch(class1 baseEntity, TableBatchOperation batch) { + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + batch.replace(secondEntity); + return secondEntity; + } + + private void insertAndDeleteBatchWithX(int x) throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + for (int m = 0; m < x; m++) { + addInsertBatch(batch); + } + + TableBatchOperation delBatch = new TableBatchOperation(); + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + for (TableResult r : results) { + Assert.assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_CREATED); + delBatch.delete((class1) r.getResult()); + } + + ArrayList<TableResult> delResults = tClient.execute(testSuiteTableName, delBatch); + for (TableResult r : delResults) { + Assert.assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + } + + private void upsertAndDeleteBatchWithX(int x) throws StorageException { + TableBatchOperation batch = new TableBatchOperation(); + for (int m = 0; m < x; m++) { + addInsertOrMergeToBatch(generateRandomEnitity("jxscl_odata"), batch); + } + + TableBatchOperation delBatch = new TableBatchOperation(); + ArrayList<TableResult> results = tClient.execute(testSuiteTableName, batch); + for (TableResult r : results) { + Assert.assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + delBatch.delete((class2) r.getResult()); + } + + ArrayList<TableResult> delResults = tClient.execute(testSuiteTableName, delBatch); + for (TableResult r : delResults) { + Assert.assertEquals(r.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java new file mode 100644 index 0000000000000..7dc7bd3c4dbd6 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java @@ -0,0 +1,268 @@ +/** + * Copyright 2011 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 static org.junit.Assert.*; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.text.DecimalFormat; +import java.util.ArrayList; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Client Tests + */ +public class TableClientTests extends TableTestBase { + @Test + public void listTablesSegmented() throws IOException, URISyntaxException, StorageException { + String tableBaseName = generateRandomTableName(); + ArrayList<String> tables = new ArrayList<String>(); + for (int m = 0; m < 20; m++) { + String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); + tClient.createTable(name); + tables.add(name); + } + + try { + int currTable = 0; + ResultSegment<String> segment1 = tClient.listTablesSegmented(tableBaseName, 5, null, null, null); + Assert.assertEquals(5, segment1.getLength()); + for (String s : segment1.getResults()) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + + ResultSegment<String> segment2 = tClient.listTablesSegmented(tableBaseName, 5, + segment1.getContinuationToken(), null, null); + Assert.assertEquals(5, segment2.getLength()); + for (String s : segment2.getResults()) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + + ResultSegment<String> segment3 = tClient.listTablesSegmented(tableBaseName, 5, + segment2.getContinuationToken(), null, null); + Assert.assertEquals(5, segment3.getLength()); + for (String s : segment3.getResults()) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + } + finally { + for (String s : tables) { + tClient.deleteTable(s); + } + } + } + + @Test + public void listTablesSegmentedNoPrefix() throws IOException, URISyntaxException, StorageException { + String tableBaseName = generateRandomTableName(); + ArrayList<String> tables = new ArrayList<String>(); + for (int m = 0; m < 20; m++) { + String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); + tClient.createTable(name); + tables.add(name); + } + + try { + int currTable = 0; + ResultSegment<String> segment1 = tClient.listTablesSegmented(null, 5, null, null, null); + Assert.assertEquals(5, segment1.getLength()); + for (String s : segment1.getResults()) { + if (s.startsWith(tableBaseName)) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + } + + ResultSegment<String> segment2 = tClient.listTablesSegmented(null, 5, segment1.getContinuationToken(), + null, null); + Assert.assertEquals(5, segment2.getLength()); + for (String s : segment2.getResults()) { + if (s.startsWith(tableBaseName)) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + } + + ResultSegment<String> segment3 = tClient.listTablesSegmented(null, 5, segment2.getContinuationToken(), + null, null); + Assert.assertEquals(5, segment3.getLength()); + for (String s : segment3.getResults()) { + if (s.startsWith(tableBaseName)) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + + } + } + finally { + for (String s : tables) { + tClient.deleteTable(s); + } + } + } + + @Test + public void listTablesWithIterator() throws IOException, URISyntaxException, StorageException { + String tableBaseName = generateRandomTableName(); + ArrayList<String> tables = new ArrayList<String>(); + for (int m = 0; m < 20; m++) { + String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); + tClient.createTable(name); + tables.add(name); + } + + try { + // With prefix + int currTable = 0; + for (String s : tClient.listTables(tableBaseName, null, null)) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } + + Assert.assertEquals(20, currTable); + + // Without prefix + currTable = 0; + for (String s : tClient.listTables()) { + if (s.startsWith(tableBaseName)) { + currTable++; + } + } + + Assert.assertEquals(20, currTable); + } + finally { + for (String s : tables) { + tClient.deleteTable(s); + } + } + } + + @Test + public void tableCreateAndAttemptCreateOnceExists() throws StorageException { + String tableName = generateRandomTableName(); + try { + tClient.createTable(tableName); + Assert.assertTrue(tClient.doesTableExist(tableName)); + + // Should fail as it already exists + try { + tClient.createTable(tableName); + fail(); + } + catch (StorageException ex) { + Assert.assertEquals(ex.getErrorCode(), "TableAlreadyExists"); + } + } + finally { + // cleanup + tClient.deleteTableIfExists(tableName); + } + } + + @Test + public void tableCreateExistsAndDelete() throws StorageException { + String tableName = generateRandomTableName(); + try { + Assert.assertTrue(tClient.createTableIfNotExists(tableName)); + Assert.assertTrue(tClient.doesTableExist(tableName)); + Assert.assertTrue(tClient.deleteTableIfExists(tableName)); + } + finally { + // cleanup + tClient.deleteTableIfExists(tableName); + } + } + + @Test + public void tableCreateIfNotExists() throws StorageException { + String tableName = generateRandomTableName(); + try { + Assert.assertTrue(tClient.createTableIfNotExists(tableName)); + Assert.assertTrue(tClient.doesTableExist(tableName)); + Assert.assertFalse(tClient.createTableIfNotExists(tableName)); + } + finally { + // cleanup + tClient.deleteTableIfExists(tableName); + } + } + + @Test + public void tableDeleteIfExists() throws StorageException { + String tableName = generateRandomTableName(); + + Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + + tClient.createTable(tableName); + Assert.assertTrue(tClient.doesTableExist(tableName)); + Assert.assertTrue(tClient.deleteTableIfExists(tableName)); + Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + } + + @Test + public void tableDeleteWhenExistAndNotExists() throws StorageException { + String tableName = generateRandomTableName(); + try { + // Should fail as it doesnt already exists + try { + tClient.deleteTable(tableName); + fail(); + } + catch (StorageException ex) { + Assert.assertEquals(ex.getMessage(), "Not Found"); + } + + tClient.createTable(tableName); + Assert.assertTrue(tClient.doesTableExist(tableName)); + tClient.deleteTable(tableName); + Assert.assertFalse(tClient.doesTableExist(tableName)); + } + finally { + tClient.deleteTableIfExists(tableName); + } + } + + @Test + public void tableDoesTableExist() throws StorageException { + String tableName = generateRandomTableName(); + try { + Assert.assertFalse(tClient.doesTableExist(tableName)); + Assert.assertTrue(tClient.createTableIfNotExists(tableName)); + Assert.assertTrue(tClient.doesTableExist(tableName)); + } + finally { + // cleanup + tClient.deleteTableIfExists(tableName); + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java new file mode 100644 index 0000000000000..ce90b73f1af4b --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java @@ -0,0 +1,231 @@ +/** + * Copyright 2011 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.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Escaping Tests + */ +public class TableEscapingTests extends TableTestBase { + @Test + public void emptyString() throws StorageException { + doEscapeTest("", false); + } + + @Test + public void emptyStringBatch() throws StorageException { + doEscapeTest("", true); + } + + @Test + public void randomChars() throws StorageException { + doEscapeTest("!$'\"()*+,;=", false); + } + + @Test + public void randomCharsBatch() throws StorageException { + doEscapeTest("!$'\"()*+,;=", true); + } + + @Test + public void regularPKInQuery() throws StorageException { + doQueryEscapeTest("data"); + } + + @Test + public void specialChars() throws StorageException { + doEscapeTest("\\ // @ ? <?", true); + } + + @Test + public void specialCharsBatch() throws StorageException { + doEscapeTest("\\ // @ ? <?", true); + } + + @Test + public void unicode() throws StorageException { + doEscapeTest("\u00A9\u770b\u5168\u90e8", false); + doEscapeTest("char中文test", false); + doEscapeTest("char中文test", false); + doEscapeTest("世界你好", false); + } + + @Test + public void unicodeBatch() throws StorageException { + doEscapeTest("\u00A9\u770b\u5168\u90e8", true); + doEscapeTest("char中文test", true); + doEscapeTest("char中文test", true); + doEscapeTest("世界你好", true); + } + + @Test + public void unicodeInQuery() throws StorageException { + doQueryEscapeTest("char中文test"); + doQueryEscapeTest("char中文test"); + doQueryEscapeTest("世界你好"); + doQueryEscapeTest("\u00A9\u770b\u5168\u90e8"); + } + + @Test + public void whiteSpaceOnly() throws StorageException { + doEscapeTest(" ", false); + } + + @Test + public void whiteSpaceOnlyBatch() throws StorageException { + doEscapeTest(" ", true); + } + + @Test + public void whiteSpaceOnlyInQuery() throws StorageException { + doQueryEscapeTest(" "); + } + + @Test + public void xmlTest() throws StorageException { + doEscapeTest("</>", false); + doEscapeTest("<tag>", false); + doEscapeTest("</entry>", false); + doEscapeTest("!<", false); + doEscapeTest("<!%^&j", false); + } + + @Test + public void xmlTestBatch() throws StorageException { + doEscapeTest("</>", false); + doEscapeTest("<tag>", false); + doEscapeTest("</entry>", false); + doEscapeTest("!<", false); + doEscapeTest("<!%^&j", false); + } + + private void doEscapeTest(String data, boolean useBatch) throws StorageException { + class1 ref = new class1(); + + ref.setA(data); + ref.setPartitionKey("temp"); + ref.setRowKey(UUID.randomUUID().toString()); + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.insert(ref); + tClient.execute(testSuiteTableName, batch); + } + else { + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + } + + TableResult res = null; + + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class); + res = tClient.execute(testSuiteTableName, batch).get(0); + } + else { + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + } + + class1 retObj = res.getResultAsType(); + Assert.assertEquals(ref.getA(), retObj.getA()); + + ref.setEtag(retObj.getEtag()); + ref.setB(data); + + // Merge + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.merge(ref); + tClient.execute(testSuiteTableName, batch); + } + else { + tClient.execute(testSuiteTableName, TableOperation.merge(ref)); + } + + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class); + res = tClient.execute(testSuiteTableName, batch).get(0); + } + else { + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + } + + retObj = res.getResultAsType(); + Assert.assertEquals(ref.getA(), retObj.getA()); + Assert.assertEquals(ref.getB(), retObj.getB()); + + // Replace + ref.setEtag(retObj.getEtag()); + ref.setC(data); + + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.replace(ref); + tClient.execute(testSuiteTableName, batch); + } + else { + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + } + + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class); + res = tClient.execute(testSuiteTableName, batch).get(0); + } + else { + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + } + + retObj = res.getResultAsType(); + Assert.assertEquals(ref.getA(), retObj.getA()); + Assert.assertEquals(ref.getB(), retObj.getB()); + Assert.assertEquals(ref.getC(), retObj.getC()); + } + + private void doQueryEscapeTest(String data) throws StorageException { + class1 ref = new class1(); + + ref.setA(data); + ref.setPartitionKey(UUID.randomUUID().toString()); + ref.setRowKey("foo"); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + TableQuery<class1> query = TableQuery.from(testSuiteTableName, class1.class).where( + String.format("(PartitionKey eq '%s') and (A eq '%s')", ref.getPartitionKey(), data)); + + int count = 0; + + for (class1 ent : tClient.execute(query)) { + count++; + Assert.assertEquals(ent.getA(), ref.getA()); + Assert.assertEquals(ent.getB(), ref.getB()); + Assert.assertEquals(ent.getC(), ref.getC()); + Assert.assertEquals(ent.getPartitionKey(), ref.getPartitionKey()); + Assert.assertEquals(ent.getRowKey(), ref.getRowKey()); + } + + Assert.assertEquals(count, 1); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableOperationTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableOperationTests.java new file mode 100644 index 0000000000000..4beb6ebc4ed21 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableOperationTests.java @@ -0,0 +1,591 @@ +/** + * Copyright 2011 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 static org.junit.Assert.*; + +import java.net.HttpURLConnection; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Operation Tests + */ +public class TableOperationTests extends TableTestBase { + @Test + public void delete() throws StorageException { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + TableOperation op = TableOperation.insert(ref); + + tClient.execute(testSuiteTableName, op); + tClient.execute(testSuiteTableName, TableOperation.delete(ref)); + + TableResult res2 = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + Assert.assertTrue(res2.getResult() == null); + } + + @Test + public void deleteFail() throws StorageException { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + String oldEtag = ref.getEtag(); + + // update entity + ref.setA("updated"); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + ref.setEtag(oldEtag); + + try { + tClient.execute(testSuiteTableName, TableOperation.delete(ref)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The update condition specified in the request was not satisfied.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied"); + } + + TableResult res2 = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + ref = res2.getResultAsType(); + // actually delete it + tClient.execute(testSuiteTableName, TableOperation.delete(ref)); + + // now try to delete it and fail + try { + tClient.execute(testSuiteTableName, TableOperation.delete(ref)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Not Found"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The specified resource does not exist.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound"); + } + } + + @Test + public void emptyRetrieve() throws StorageException { + class1 ref = new class1(); + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + Assert.assertNull(res.getResult()); + Assert.assertEquals(res.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + + @Test + public void insertOrMerge() throws StorageException { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tClient.execute(testSuiteTableName, TableOperation.insertOrMerge(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tClient.execute(testSuiteTableName, TableOperation.insertOrMerge(secondEntity)); + + // Retrieve entity + TableResult queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + + @Test + public void insertOrReplace() throws StorageException { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or replace Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tClient.execute(testSuiteTableName, TableOperation.insertOrReplace(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tClient.execute(testSuiteTableName, TableOperation.insertOrReplace(secondEntity)); + + // Retrieve entity + TableResult queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult.getResultAsType(); + + // Validate old properties dont exist + Assert.assertTrue(retrievedEntity.getProperties().get("A") == null); + Assert.assertTrue(retrievedEntity.getProperties().get("B") == null); + Assert.assertTrue(retrievedEntity.getProperties().get("C") == null); + Assert.assertTrue(retrievedEntity.getProperties().get("D") == null); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + + @Test + public void merge() throws StorageException { + // Insert base entity + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity)); + + TableResult res2 = tClient.execute(testSuiteTableName, TableOperation.retrieve(secondEntity.getPartitionKey(), + secondEntity.getRowKey(), DynamicTableEntity.class)); + DynamicTableEntity mergedEntity = (DynamicTableEntity) res2.getResult(); + + Assert.assertNotNull("Property A", mergedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), mergedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", mergedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), mergedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", mergedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), mergedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", mergedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), mergedEntity.getProperties().get("D").getValueAsByteArray())); + + Assert.assertNotNull("Property L", mergedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), mergedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", mergedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), mergedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", mergedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), mergedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", mergedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), mergedEntity.getProperties().get("O").getValueAsString()); + } + + @Test + public void mergeFail() throws StorageException { + // Insert base entity + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + String oldEtag = baseEntity.getEtag(); + + tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity)); + + secondEntity.setEtag(oldEtag); + secondEntity.setL("updated"); + try { + tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The update condition specified in the request was not satisfied.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "UpdateConditionNotSatisfied"); + } + + // delete entity + TableResult queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult.getResultAsType(); + tClient.execute(testSuiteTableName, TableOperation.delete(retrievedEntity)); + + try { + tClient.execute(testSuiteTableName, TableOperation.merge(secondEntity)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Not Found"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The specified resource does not exist.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound"); + } + } + + @Test + public void retrieveWithoutResolver() throws StorageException { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + @SuppressWarnings("unused") + class1 retrievedEnt = res.getResultAsType(); + + Assert.assertEquals(((class1) res.getResult()).getA(), ref.getA()); + } + + @Test + public void retrieveWithResolver() throws StorageException { + class1 ref = new class1(); + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + TableOperation op = TableOperation.insert(ref); + + tClient.execute(testSuiteTableName, op); + + TableResult res4 = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), new EntityResolver<String>() { + @Override + public String resolve(String partitionKey, String rowKey, Date timeStamp, + HashMap<String, EntityProperty> properties, String etag) { + return properties.get("A").getValueAsString(); + } + })); + + Assert.assertEquals(res4.getResult().toString(), ref.getA()); + } + + @Test + public void retrieveWithNullResolver() throws StorageException { + try { + TableOperation.retrieve("foo", "blah", (EntityResolver<?>) null); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Query requires a valid class type or resolver."); + } + } + + @Test + public void insertFail() throws StorageException { + class1 ref = new class1(); + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + TableOperation op = TableOperation.insert(ref); + + tClient.execute(testSuiteTableName, op); + try { + tClient.execute(testSuiteTableName, op); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Conflict"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The specified entity already exists")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityAlreadyExists"); + } + } + + @Test + public void replace() throws StorageException { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + // Insert entity + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + TableResult queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + // Retrieve entity + DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Remove property and update + retrievedEntity.getProperties().remove("D"); + + tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity)); + + // Retrieve Entity + queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + + // Validate + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertTrue(retrievedEntity.getProperties().get("D") == null); + } + + @Test + public void replaceFail() throws StorageException { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + // Insert entity + tClient.execute(testSuiteTableName, TableOperation.insert(baseEntity)); + + String oldEtag = baseEntity.getEtag(); + + TableResult queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + // Retrieve entity + DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Remove property and update + retrievedEntity.getProperties().remove("D"); + + tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity)); + + retrievedEntity.setEtag(oldEtag); + + try { + tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Precondition Failed"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The condition specified using HTTP conditional header(s) is not met.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ConditionNotMet"); + } + + // delete entity + queryResult = tClient + .execute(testSuiteTableName, TableOperation.retrieve(baseEntity.getPartitionKey(), + baseEntity.getRowKey(), DynamicTableEntity.class)); + + tClient.execute(testSuiteTableName, TableOperation.delete((DynamicTableEntity) queryResult.getResultAsType())); + + try { + tClient.execute(testSuiteTableName, TableOperation.replace(retrievedEntity)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Not Found"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The specified resource does not exist.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "ResourceNotFound"); + } + } + + @Test + public void insertEntityOver1MB() throws StorageException { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + // 1mb right here + ref.setD(new byte[1024 * 1024]); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + try { + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("The entity is larger than allowed by the Table Service.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "EntityTooLarge"); + } + } + + @Test + public void insertEntityWithPropertyMoreThan255chars() throws StorageException { + DynamicTableEntity ref = new DynamicTableEntity(); + + String propName = ""; + for (int m = 0; m < 255; m++) { + propName.concat(Integer.toString(m % 9)); + } + + ref.getProperties().put(propName, new EntityProperty("test")); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + try { + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("One of the request inputs is not valid.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "InvalidInput"); + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java new file mode 100644 index 0000000000000..dd46b99bae991 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java @@ -0,0 +1,427 @@ +/** + * Copyright 2011 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 static org.junit.Assert.*; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.BeforeClass; +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ResponseReceivedEvent; +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageEvent; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.table.client.TableQuery.QueryComparisons; + +/** + * Table Query Tests + */ +public class TableQueryTests extends TableTestBase { + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + TableTestBase.setup(); + + // Insert 500 entities in Batches to query + for (int i = 0; i < 5; i++) { + TableBatchOperation batch = new TableBatchOperation(); + + for (int j = 0; j < 100; j++) { + class1 ent = generateRandomEnitity("javatables_batch_" + Integer.toString(i)); + ent.setRowKey(String.format("%06d", j)); + batch.insert(ent); + } + + tClient.execute(testSuiteTableName, batch); + } + } + + @Test + public void tableQueryWithDynamicEntity() { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + + final Iterable<DynamicTableEntity> result = tClient.execute(TableQuery.from(testSuiteTableName, + DynamicTableEntity.class)); + + // Validate results + for (DynamicTableEntity ent : result) { + Assert.assertEquals(ent.getProperties().size(), 4); + Assert.assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA()); + Assert.assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB()); + Assert.assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC()); + Assert.assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD())); + } + } + + @Test + public void tableQueryWithProjection() { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + final Iterable<class1> result = tClient.execute(TableQuery.from(testSuiteTableName, class1.class).select( + new String[] { "A", "C" })); + + // Validate results + for (class1 ent : result) { + // Validate core properties were sent. + Assert.assertNotNull(ent.getPartitionKey()); + Assert.assertNotNull(ent.getRowKey()); + Assert.assertNotNull(ent.getTimestamp()); + + // Validate correct columsn returned. + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), null); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getD(), null); + } + } + + @Test + public void ensureSelectOnlySendsReservedColumnsOnce() { + OperationContext opContext = new OperationContext(); + opContext.getResponseReceivedEventHandler().addListener(new StorageEvent<ResponseReceivedEvent>() { + + @Override + public void eventOccurred(ResponseReceivedEvent eventArg) { + HttpURLConnection conn = (HttpURLConnection) eventArg.getConnectionObject(); + + String urlString = conn.getURL().toString(); + + Assert.assertEquals(urlString.indexOf("PartitionKey"), urlString.lastIndexOf("PartitionKey")); + Assert.assertEquals(urlString.indexOf("RowKey"), urlString.lastIndexOf("RowKey")); + Assert.assertEquals(urlString.indexOf("Timestamp"), urlString.lastIndexOf("Timestamp")); + } + }); + + final Iterable<class1> result = tClient.execute( + TableQuery.from(testSuiteTableName, class1.class).select( + new String[] { "PartitionKey", "RowKey", "Timestamp" }), null, opContext); + + // Validate results + for (class1 ent : result) { + Assert.assertEquals(ent.getA(), null); + Assert.assertEquals(ent.getB(), null); + Assert.assertEquals(ent.getC(), null); + Assert.assertEquals(ent.getD(), null); + } + } + + @Test + public void tableQueryWithReflection() { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + + final Iterable<class1> result = tClient.execute(TableQuery.from(testSuiteTableName, class1.class)); + + // Validate results + for (class1 ent : result) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertTrue(Arrays.equals(ent.getD(), randEnt.getD())); + } + } + + @Test + public void tableQueryWithResolver() { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + + final Iterable<class1> result = tClient.execute(TableQuery.from(testSuiteTableName, TableServiceEntity.class), + new EntityResolver<class1>() { + @Override + public class1 resolve(String partitionKey, String rowKey, Date timeStamp, + HashMap<String, EntityProperty> properties, String etag) { + Assert.assertEquals(properties.size(), 4); + class1 ref = new class1(); + ref.setA(properties.get("A").getValueAsString()); + ref.setB(properties.get("B").getValueAsString()); + ref.setC(properties.get("C").getValueAsString()); + ref.setD(properties.get("D").getValueAsByteArray()); + return ref; + } + }); + + // Validate results + for (class1 ent : result) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertTrue(Arrays.equals(ent.getD(), randEnt.getD())); + } + } + + @Test + public void tableQueryWithTake() throws IOException, URISyntaxException, StorageException { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + final ResultSegment<class1> result = tClient.executeSegmented(TableQuery.from(testSuiteTableName, class1.class) + .select(new String[] { "A", "C" }).take(25), null); + + int count = 0; + // Validate results + for (class1 ent : result.getResults()) { + count++; + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), null); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getD(), null); + } + + Assert.assertEquals(count, 25); + } + + @Test + public void tableQueryWithFilter() throws StorageException { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery<class1> query = TableQuery.from(testSuiteTableName, class1.class).where( + String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); + + int count = 0; + + for (class1 ent : tClient.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + } + + @Test + public void tableQueryWithContinuation() throws StorageException { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery<class1> query = TableQuery.from(testSuiteTableName, class1.class) + .where(String.format("(PartitionKey ge '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")) + .take(25); + + // take will cause the query to return 25 at a time + + int count = 0; + int pk = 1; + for (class1 ent : tClient.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_" + Integer.toString(pk)); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count % 50 + 50)); + count++; + + if (count % 50 == 0) { + pk++; + } + } + + Assert.assertEquals(count, 200); + } + + @Test + public void testQueryWithNullClassType() throws StorageException { + try { + TableQuery.from(testSuiteTableName, null); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Query requires a valid class type."); + } + } + + @Test + public void testQueryWithInvalidTakeCount() throws StorageException { + try { + TableQuery.from(testSuiteTableName, TableServiceEntity.class).take(0); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Take count must be positive and greater than 0."); + } + + try { + TableQuery.from(testSuiteTableName, TableServiceEntity.class).take(-1); + } + catch (IllegalArgumentException ex) { + Assert.assertEquals(ex.getMessage(), "Take count must be positive and greater than 0."); + } + } + + @Test + public void tableInvalidQuery() throws StorageException, IOException, URISyntaxException { + TableQuery<class1> query = TableQuery.from(testSuiteTableName, class1.class).where( + String.format("(PartitionKey ) and (RowKey ge '%s')", "javatables_batch_1", "000050")); + try { + tClient.executeSegmented(query, null); + fail(); + } + catch (TableServiceException ex) { + Assert.assertEquals(ex.getMessage(), "Bad Request"); + Assert.assertTrue(ex.getExtendedErrorInformation().getErrorMessage() + .startsWith("One of the request inputs is not valid.")); + Assert.assertEquals(ex.getExtendedErrorInformation().getErrorCode(), "InvalidInput"); + } + } + + @Test + public void testQueryOnSupportedTypes() throws StorageException { + // Setup + TableBatchOperation batch = new TableBatchOperation(); + String pk = UUID.randomUUID().toString(); + + ComplexEntity middleRef = null; + + for (int j = 0; j < 100; j++) { + ComplexEntity ent = new ComplexEntity(); + ent.setPartitionKey(pk); + ent.setRowKey(String.format("%04d", j)); + ent.setBinary(new Byte[] { 0x01, 0x02, (byte) j }); + ent.setBinaryPrimitive(new byte[] { 0x01, 0x02, (byte) j }); + ent.setBool(j % 2 == 0 ? true : false); + ent.setBoolPrimitive(j % 2 == 0 ? true : false); + ent.setDateTime(new Date()); + ent.setDouble(j + ((double) j) / 100); + ent.setDoublePrimitive(j + ((double) j) / 100); + ent.setInt32(j); + ent.setInt64((long) j); + ent.setIntegerPrimitive(j); + ent.setLongPrimitive(j); + ent.setGuid(UUID.randomUUID()); + ent.setString(String.format("%04d", j)); + + try { + // Add delay to make times unique + Thread.sleep(100); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + batch.insert(ent); + if (j == 50) { + middleRef = ent; + } + } + + tClient.execute(testSuiteTableName, batch); + + try { + // 1. Filter on String + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("String", QueryComparisons.GREATER_THAN_OR_EQUAL, "0050"), 50); + + // 2. Filter on UUID + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Guid", QueryComparisons.EQUAL, middleRef.getGuid()), 1); + + // 3. Filter on Long + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Int64", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getInt64()), 50); + + executeQueryAndAssertResults(TableQuery.generateFilterCondition("LongPrimitive", + QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getInt64()), 50); + + // 4. Filter on Double + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Double", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getDouble()), 50); + + executeQueryAndAssertResults(TableQuery.generateFilterCondition("DoublePrimitive", + QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getDouble()), 50); + + // 5. Filter on Integer + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Int32", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getInt32()), 50); + + executeQueryAndAssertResults(TableQuery.generateFilterCondition("IntegerPrimitive", + QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getInt32()), 50); + + // 6. Filter on Date + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("DateTime", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getDateTime()), 50); + + // 7. Filter on Boolean + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Bool", QueryComparisons.EQUAL, middleRef.getBool()), 50); + + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("BoolPrimitive", QueryComparisons.EQUAL, middleRef.getBool()), + 50); + + // 8. Filter on Binary + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Binary", QueryComparisons.EQUAL, middleRef.getBinary()), 1); + + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("BinaryPrimitive", QueryComparisons.EQUAL, + middleRef.getBinaryPrimitive()), 1); + + // 9. Filter on Binary GTE + executeQueryAndAssertResults( + TableQuery.generateFilterCondition("Binary", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getBinary()), 50); + + executeQueryAndAssertResults(TableQuery.generateFilterCondition("BinaryPrimitive", + QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getBinaryPrimitive()), 50); + + // 10. Complex Filter on Binary GTE + executeQueryAndAssertResults(TableQuery.combineFilters( + TableQuery.generateFilterCondition(TableConstants.PARTITION_KEY, QueryComparisons.EQUAL, + middleRef.getPartitionKey()), + TableQuery.Operators.AND, + TableQuery.generateFilterCondition("Binary", QueryComparisons.GREATER_THAN_OR_EQUAL, + middleRef.getBinary())), 50); + + executeQueryAndAssertResults(TableQuery.generateFilterCondition("BinaryPrimitive", + QueryComparisons.GREATER_THAN_OR_EQUAL, middleRef.getBinaryPrimitive()), 50); + + } + finally { + // cleanup + TableBatchOperation delBatch = new TableBatchOperation(); + TableQuery<ComplexEntity> query = TableQuery.from(testSuiteTableName, ComplexEntity.class).where( + String.format("PartitionKey eq '%s'", pk)); + + for (ComplexEntity e : tClient.execute(query)) { + delBatch.delete(e); + } + + tClient.execute(testSuiteTableName, delBatch); + } + } + + private void executeQueryAndAssertResults(String filter, int expectedResults) { + int count = 0; + TableQuery<ComplexEntity> query = TableQuery.from(testSuiteTableName, ComplexEntity.class).where(filter); + for (@SuppressWarnings("unused") + ComplexEntity e : tClient.execute(query)) { + count++; + } + + Assert.assertEquals(expectedResults, count); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableSerializerTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableSerializerTests.java new file mode 100644 index 0000000000000..e7380c67c6fe6 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableSerializerTests.java @@ -0,0 +1,269 @@ +/** + * Copyright 2011 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.IOException; +import java.net.URISyntaxException; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Serializer Tests + */ +public class TableSerializerTests extends TableTestBase { + @Test + public void testComplexEntityInsert() throws IOException, URISyntaxException, StorageException { + ComplexEntity ref = new ComplexEntity(); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + ref.populateEntity(); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ComplexEntity retrievedComplexRef = res.getResultAsType(); + ref.assertEquality(retrievedComplexRef); + } + + @Test + public void testIgnoreAnnotation() throws IOException, URISyntaxException, StorageException { + // Ignore On Getter + IgnoreOnGetter ignoreGetter = new IgnoreOnGetter(); + ignoreGetter.setPartitionKey("jxscl_odata"); + ignoreGetter.setRowKey(UUID.randomUUID().toString()); + ignoreGetter.setIgnoreString("ignore data"); + + tClient.execute(testSuiteTableName, TableOperation.insert(ignoreGetter)); + + TableResult res = tClient + .execute(testSuiteTableName, TableOperation.retrieve(ignoreGetter.getPartitionKey(), + ignoreGetter.getRowKey(), IgnoreOnGetter.class)); + + IgnoreOnGetter retrievedIgnoreG = res.getResultAsType(); + Assert.assertEquals(retrievedIgnoreG.getIgnoreString(), null); + + // Ignore On Setter + IgnoreOnSetter ignoreSetter = new IgnoreOnSetter(); + ignoreSetter.setPartitionKey("jxscl_odata"); + ignoreSetter.setRowKey(UUID.randomUUID().toString()); + ignoreSetter.setIgnoreString("ignore data"); + + tClient.execute(testSuiteTableName, TableOperation.insert(ignoreSetter)); + + res = tClient + .execute(testSuiteTableName, TableOperation.retrieve(ignoreSetter.getPartitionKey(), + ignoreSetter.getRowKey(), IgnoreOnSetter.class)); + + IgnoreOnSetter retrievedIgnoreS = res.getResultAsType(); + Assert.assertEquals(retrievedIgnoreS.getIgnoreString(), null); + + // Ignore On Getter AndSetter + IgnoreOnGetterAndSetter ignoreGetterSetter = new IgnoreOnGetterAndSetter(); + ignoreGetterSetter.setPartitionKey("jxscl_odata"); + ignoreGetterSetter.setRowKey(UUID.randomUUID().toString()); + ignoreGetterSetter.setIgnoreString("ignore data"); + + tClient.execute(testSuiteTableName, TableOperation.insert(ignoreGetterSetter)); + + res = tClient.execute(testSuiteTableName, TableOperation.retrieve(ignoreGetterSetter.getPartitionKey(), + ignoreGetterSetter.getRowKey(), IgnoreOnGetterAndSetter.class)); + + IgnoreOnGetterAndSetter retrievedIgnoreGS = res.getResultAsType(); + Assert.assertEquals(retrievedIgnoreGS.getIgnoreString(), null); + } + + @Test + public void testNulls() throws IOException, URISyntaxException, StorageException { + ComplexEntity ref = new ComplexEntity(); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + ref.populateEntity(); + + // Binary object + ref.setBinary(null); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + ref = res.getResultAsType(); + + Assert.assertNull("Binary should be null", ref.getBinary()); + + // Bool + ref.setBool(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("Bool should be null", ref.getBool()); + + // Date + ref.setDateTime(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("Date should be null", ref.getDateTime()); + + // Double + ref.setDouble(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("Double should be null", ref.getDouble()); + + // UUID + ref.setGuid(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("UUID should be null", ref.getGuid()); + + // Int32 + ref.setInt32(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("Int32 should be null", ref.getInt32()); + + // Int64 + ref.setInt64(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("Int64 should be null", ref.getInt64()); + + // String + ref.setString(null); + tClient.execute(testSuiteTableName, TableOperation.replace(ref)); + + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ref = res.getResultAsType(); + + Assert.assertNull("String should be null", ref.getString()); + } + + @Test + public void testStoreAsAnnotation() throws IOException, URISyntaxException, StorageException { + StoreAsEntity ref = new StoreAsEntity(); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + ref.setStoreAsString("StoreAsOverride Data"); + ref.populateEntity(); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), StoreAsEntity.class)); + + StoreAsEntity retrievedStoreAsRef = res.getResultAsType(); + Assert.assertEquals(retrievedStoreAsRef.getStoreAsString(), ref.getStoreAsString()); + + // Same query with a class without the storeAs annotation + res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), ComplexEntity.class)); + + ComplexEntity retrievedComplexRef = res.getResultAsType(); + Assert.assertEquals(retrievedComplexRef.getString(), ref.getStoreAsString()); + + tClient.execute(testSuiteTableName, TableOperation.delete(retrievedComplexRef)); + } + + @Test + public void testInvalidStoreAsAnnotation() throws IOException, URISyntaxException, StorageException { + InvalidStoreAsEntity ref = new InvalidStoreAsEntity(); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + ref.setStoreAsString("StoreAsOverride Data"); + ref.populateEntity(); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), InvalidStoreAsEntity.class)); + + InvalidStoreAsEntity retrievedStoreAsRef = res.getResultAsType(); + Assert.assertEquals(retrievedStoreAsRef.getStoreAsString(), null); + } + + @Test + public void whitespaceTest() throws StorageException { + class1 ref = new class1(); + + ref.setA("B "); + ref.setB(" A "); + ref.setC(" "); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + Assert.assertEquals(((class1) res.getResult()).getA(), ref.getA()); + } + + @Test + public void newLineTest() throws StorageException { + class1 ref = new class1(); + + ref.setA("B "); + ref.setB(" A "); + ref.setC("\r\n"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey("jxscl_odata"); + ref.setRowKey(UUID.randomUUID().toString()); + + tClient.execute(testSuiteTableName, TableOperation.insert(ref)); + + TableResult res = tClient.execute(testSuiteTableName, + TableOperation.retrieve(ref.getPartitionKey(), ref.getRowKey(), class1.class)); + + Assert.assertEquals(((class1) res.getResult()).getA(), ref.getA()); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java new file mode 100644 index 0000000000000..b30f200fa61a9 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java @@ -0,0 +1,599 @@ +/** + * Copyright 2011 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.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.microsoft.windowsazure.services.blob.client.CloudBlobClient; +import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.queue.client.CloudQueueClient; + +/** + * Table Test Base + */ +public class TableTestBase { + public static boolean USE_DEV_FABRIC = false; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=http;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + + public static class class1 extends TableServiceEntity { + public String A; + + public String B; + + public String C; + + public byte[] D; + + public class1() { + // empty ctor + } + + public synchronized String getA() { + return this.A; + } + + public synchronized String getB() { + return this.B; + } + + public synchronized String getC() { + return this.C; + } + + public synchronized byte[] getD() { + return this.D; + } + + public synchronized void setA(final String a) { + this.A = a; + } + + public synchronized void setB(final String b) { + this.B = b; + } + + public synchronized void setC(final String c) { + this.C = c; + } + + public synchronized void setD(final byte[] d) { + this.D = d; + } + } + + public class class2 extends TableServiceEntity { + private String L; + private String M; + + private String N; + + private String O; + + /** + * @return the l + */ + public String getL() { + return this.L; + } + + /** + * @return the m + */ + public String getM() { + return this.M; + } + + /** + * @return the n + */ + public String getN() { + return this.N; + } + + /** + * @return the o + */ + public String getO() { + return this.O; + } + + /** + * @param l + * the l to set + */ + public void setL(String l) { + this.L = l; + } + + /** + * @param m + * the m to set + */ + public void setM(String m) { + this.M = m; + } + + /** + * @param n + * the n to set + */ + public void setN(String n) { + this.N = n; + } + + /** + * @param o + * the o to set + */ + public void setO(String o) { + this.O = o; + } + } + + public static class ComplexEntity extends TableServiceEntity { + private Date dateTime = null; + private Boolean Bool = null; + private boolean BoolPrimitive = false; + private Byte[] Binary = null; + private byte[] binaryPrimitive = null; + private double DoublePrimitive = -1; + private Double Double = null; + private UUID Guid = null; + private int IntegerPrimitive = -1; + private Integer Int32 = null; + private long LongPrimitive = -1L; + private Long Int64 = null; + private String String = null; + + public ComplexEntity() { + // Empty Ctor + } + + public void assertEquality(ComplexEntity other) { + Assert.assertEquals(this.getPartitionKey(), other.getPartitionKey()); + Assert.assertEquals(this.getRowKey(), other.getRowKey()); + + Assert.assertEquals(this.getDateTime(), other.getDateTime()); + Assert.assertEquals(this.getGuid(), other.getGuid()); + Assert.assertEquals(this.getString(), other.getString()); + + Assert.assertEquals(this.getDouble(), other.getDouble()); + Assert.assertEquals(this.getDoublePrimitive(), other.getDoublePrimitive()); + Assert.assertEquals(this.getInt32(), other.getInt32()); + Assert.assertEquals(this.getIntegerPrimitive(), other.getIntegerPrimitive()); + Assert.assertEquals(this.getBool(), other.getBool()); + Assert.assertEquals(this.getBoolPrimitive(), other.getBoolPrimitive()); + Assert.assertEquals(this.getInt64(), other.getInt64()); + Assert.assertEquals(this.getIntegerPrimitive(), other.getIntegerPrimitive()); + Assert.assertTrue(Arrays.equals(this.getBinary(), other.getBinary())); + Assert.assertTrue(Arrays.equals(this.getBinaryPrimitive(), other.getBinaryPrimitive())); + } + + /** + * @return the binary + */ + public Byte[] getBinary() { + return this.Binary; + } + + /** + * @return the binaryPrimitive + */ + public byte[] getBinaryPrimitive() { + return this.binaryPrimitive; + } + + /** + * @return the bool + */ + public Boolean getBool() { + return this.Bool; + } + + /** + * @return the bool + */ + public boolean getBoolPrimitive() { + return this.BoolPrimitive; + } + + /** + * @return the dateTime + */ + public Date getDateTime() { + return this.dateTime; + } + + /** + * @return the double + */ + public Double getDouble() { + return this.Double; + } + + /** + * @return the doublePrimitive + */ + public double getDoublePrimitive() { + return this.DoublePrimitive; + } + + /** + * @return the guid + */ + public UUID getGuid() { + return this.Guid; + } + + /** + * @return the int32 + */ + public Integer getInt32() { + return this.Int32; + } + + /** + * @return the int64 + */ + public Long getInt64() { + return this.Int64; + } + + /** + * @return the integerPrimitive + */ + public int getIntegerPrimitive() { + return this.IntegerPrimitive; + } + + /** + * @return the longPrimitive + */ + public long getLongPrimitive() { + return this.LongPrimitive; + } + + /** + * @return the string + */ + public String getString() { + return this.String; + } + + public void populateEntity() { + this.setBinary(new Byte[] { 1, 2, 3, 4 }); + this.setBinaryPrimitive(new byte[] { 1, 2, 3, 4 }); + this.setBool(true); + this.setBoolPrimitive(true); + this.setDateTime(new Date()); + this.setDouble(2342.2342); + this.setDoublePrimitive(2349879.2342); + this.setInt32(2342); + this.setInt64((long) 87987987); + this.setIntegerPrimitive(2342); + this.setLongPrimitive(87987987); + this.setGuid(UUID.randomUUID()); + this.setString("foo"); + } + + /** + * @param binary + * the binary to set + */ + public void setBinary(final Byte[] binary) { + this.Binary = binary; + } + + /** + * @param binaryPrimitive + * the binaryPrimitive to set + */ + public void setBinaryPrimitive(byte[] binaryPrimitive) { + this.binaryPrimitive = binaryPrimitive; + } + + /** + * @param bool + * the bool to set + */ + public void setBool(final Boolean bool) { + this.Bool = bool; + } + + /** + * @param boolPrimitive + * the boolPrimitive to set + */ + public void setBoolPrimitive(boolean boolPrimitive) { + this.BoolPrimitive = boolPrimitive; + } + + /** + * @param dateTime + * the dateTime to set + */ + public void setDateTime(final Date dateTime) { + this.dateTime = dateTime; + } + + /** + * @param d + * the double to set + */ + public void setDouble(final Double d) { + this.Double = d; + } + + /** + * @param doublePrimitive + * the doublePrimitive to set + */ + public void setDoublePrimitive(double doublePrimitive) { + this.DoublePrimitive = doublePrimitive; + } + + /** + * @param guid + * the guid to set + */ + public void setGuid(final UUID guid) { + this.Guid = guid; + } + + /** + * @param int32 + * the int32 to set + */ + public void setInt32(final Integer int32) { + this.Int32 = int32; + } + + /** + * @param int64 + * the int64 to set + */ + public void setInt64(final Long int64) { + this.Int64 = int64; + } + + /** + * @param integerPrimitive + * the integerPrimitive to set + */ + public void setIntegerPrimitive(int integerPrimitive) { + this.IntegerPrimitive = integerPrimitive; + } + + /** + * @param longPrimitive + * the longPrimitive to set + */ + public void setLongPrimitive(long longPrimitive) { + this.LongPrimitive = longPrimitive; + } + + /** + * @param string + * the string to set + */ + public void setString(final String string) { + this.String = string; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(java.lang.String.format("%s:%s\n", "PK", this.getPartitionKey())); + builder.append(java.lang.String.format("%s:%s\n", "RK", this.getRowKey())); + builder.append(java.lang.String.format("%s:%s\n", "Timestamp", this.getTimestamp())); + builder.append(java.lang.String.format("%s:%s\n", "etag", this.getEtag())); + + builder.append(java.lang.String.format("%s:%s\n", "DateTime", this.getDateTime())); + builder.append(java.lang.String.format("%s:%s\n", "Bool", this.getBool())); + builder.append(java.lang.String.format("%s:%s\n", "Binary", this.getBinary())); + builder.append(java.lang.String.format("%s:%s\n", "Double", this.getDouble())); + builder.append(java.lang.String.format("%s:%s\n", "Guid", this.getGuid())); + builder.append(java.lang.String.format("%s:%s\n", "Int32", this.getInt32())); + builder.append(java.lang.String.format("%s:%s\n", "Int64", this.getInt64())); + builder.append(java.lang.String.format("%s:%s\n", "String", this.getString())); + + return builder.toString(); + } + } + + public static class IgnoreOnGetter extends class1 { + private String tString = null; + + /** + * @return the string + */ + @Ignore + public String getIgnoreString() { + return this.tString; + } + + /** + * @param string + * the string to set + */ + + public void setIgnoreString(final String string) { + this.tString = string; + } + } + + public static class IgnoreOnGetterAndSetter extends class1 { + private String tString = null; + + /** + * @return the string + */ + @Ignore + public String getIgnoreString() { + return this.tString; + } + + /** + * @param string + * the string to set + */ + @Ignore + public void setIgnoreString(final String string) { + this.tString = string; + } + } + + public static class IgnoreOnSetter extends class1 { + private String tString = null; + + /** + * @return the string + */ + public String getIgnoreString() { + return this.tString; + } + + /** + * @param string + * the string to set + */ + @Ignore + public void setIgnoreString(final String string) { + this.tString = string; + } + } + + public static class StoreAsEntity extends ComplexEntity { + private String storeAsString = null; + + /** + * @return the string + */ + @StoreAs(name = "String") + public String getStoreAsString() { + return this.storeAsString; + } + + /** + * @param string + * the string to set + */ + @StoreAs(name = "String") + public void setStoreAsString(final String string) { + this.storeAsString = string; + } + } + + public static class InvalidStoreAsEntity extends ComplexEntity { + private String storeAsString = null; + + /** + * @return the string + */ + @StoreAs(name = "PartitionKey") + public String getStoreAsString() { + return this.storeAsString; + } + + /** + * @param string + * the string to set + */ + @StoreAs(name = "PartitionKey") + public void setStoreAsString(final String string) { + this.storeAsString = string; + } + } + + public static class TableEnt extends TableServiceEntity { + String TableName; + + /** + * @return the tableName + */ + public String getTableName() { + return this.TableName; + } + + /** + * @param tableName + * the tableName to set + */ + public void setTableName(String tableName) { + this.TableName = tableName; + } + } + + protected static CloudStorageAccount httpAcc; + protected static CloudBlobClient bClient; + protected static CloudQueueClient qClient; + protected static CloudTableClient tClient; + protected static String testSuiteTableName = generateRandomTableName(); + + public static class1 generateRandomEnitity(String pk) { + class1 ref = new class1(); + + ref.setA("foo_A"); + ref.setB("foo_B"); + ref.setC("foo_C"); + ref.setD(new byte[] { 0, 1, 2 }); + ref.setPartitionKey(pk); + ref.setRowKey(UUID.randomUUID().toString()); + return ref; + } + + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + + // UNCOMMENT TO USE FIDDLER + // System.setProperty("http.proxyHost", "localhost"); + // System.setProperty("http.proxyPort", "8888"); + // System.setProperty("https.proxyHost", "localhost"); + // System.setProperty("https.proxyPort", "8888"); + if (USE_DEV_FABRIC) { + httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); + } + else { + httpAcc = CloudStorageAccount.parse(CLOUD_ACCOUNT_HTTP); + } + + bClient = httpAcc.createCloudBlobClient(); + tClient = httpAcc.createCloudTableClient(); + qClient = httpAcc.createCloudQueueClient(); + testSuiteTableName = generateRandomTableName(); + tClient.createTable(testSuiteTableName); + } + + @AfterClass + public static void teardown() throws StorageException { + tClient.deleteTable(testSuiteTableName); + } + + protected static String generateRandomTableName() { + String tableName = "table" + UUID.randomUUID().toString(); + return tableName.replace("-", ""); + } +} From c4206d75d6060e661a9109d3119daa730d34ac6c Mon Sep 17 00:00:00 2001 From: Louis DeJardin <lodejard@microsoft.com> Date: Mon, 13 Feb 2012 16:38:06 -0800 Subject: [PATCH 24/76] Combining listTables and queryTables operations. Fixes #216 Parameterless listTables and queryTables have identical behavior. Add prefix property to QueryTablesOptions. If prefix and query filter are both provided, they are combined by an 'and' expression. --- .../services/table/TableContract.java | 5 -- .../TableExceptionProcessor.java | 27 ---------- .../table/implementation/TableRestProxy.java | 49 +++++++++++-------- .../table/models/ListTablesOptions.java | 20 -------- .../table/models/QueryTablesOptions.java | 10 ++++ .../table/TableServiceIntegrationTest.java | 25 +++------- 6 files changed, 45 insertions(+), 91 deletions(-) delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 63ec2c1b20865..233f3d8001405 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -24,7 +24,6 @@ import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; @@ -54,10 +53,6 @@ public interface TableContract extends FilterableService<TableContract> { GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException; - QueryTablesResult listTables() throws ServiceException; - - QueryTablesResult listTables(ListTablesOptions options) throws ServiceException; - QueryTablesResult queryTables() throws ServiceException; QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index c8bf695b7cdda..8f4bc110d4da5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -31,7 +31,6 @@ import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; @@ -222,32 +221,6 @@ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceE } } - @Override - public QueryTablesResult listTables() throws ServiceException { - try { - return service.listTables(); - } - catch (UniformInterfaceException e) { - throw processCatch(new ServiceException(e)); - } - catch (ClientHandlerException e) { - throw processCatch(new ServiceException(e)); - } - } - - @Override - public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { - try { - return service.listTables(options); - } - catch (UniformInterfaceException e) { - throw processCatch(new ServiceException(e)); - } - catch (ClientHandlerException e) { - throw processCatch(new ServiceException(e)); - } - } - @Override public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException { try { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 096c5b4ec312e..2e32f907f46c0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -67,7 +67,6 @@ import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.LitteralFilter; import com.microsoft.windowsazure.services.table.models.Query; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; @@ -311,23 +310,6 @@ public GetTableResult getTable(String table, TableServiceOptions options) throws return result; } - @Override - public QueryTablesResult listTables() throws ServiceException { - return listTables(new ListTablesOptions()); - } - - @Override - public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { - // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> uppperBound is prefix + '{' - Filter filter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(options.getPrefix())), - Filter.le(Filter.litteral("TableName"), Filter.constant(options.getPrefix() + "{"))); - - QueryTablesOptions queryTableOptions = new QueryTablesOptions(); - queryTableOptions.setTimeout(options.getTimeout()); - queryTableOptions.setQuery(new Query().setFilter(filter)); - return queryTables(queryTableOptions); - } - @Override public QueryTablesResult queryTables() throws ServiceException { return queryTables(new QueryTablesOptions()); @@ -335,9 +317,36 @@ public QueryTablesResult queryTables() throws ServiceException { @Override public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { + + Query query = options.getQuery(); + String nextTableName = options.getNextTableName(); + String prefix = options.getPrefix(); + + if (prefix != null) { + // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound is prefix + '{' + Filter prefixFilter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(prefix)), + Filter.le(Filter.litteral("TableName"), Filter.constant(prefix + "{"))); + + // a new query is needed if prefix alone is passed in + if (query == null) { + query = new Query(); + } + + // examine the existing filter on the query + if (query.getFilter() == null) { + // use the prefix filter if the query filter is null + query.setFilter(prefixFilter); + } + else { + // combine and use the prefix filter if the query filter exists + Filter combinedFilter = Filter.and(query.getFilter(), prefixFilter); + query.setFilter(combinedFilter); + } + } + WebResource webResource = getResource(options).path("Tables"); - webResource = addOptionalQuery(webResource, options.getQuery()); - webResource = addOptionalQueryParam(webResource, "NextTableName", options.getNextTableName()); + webResource = addOptionalQuery(webResource, query); + webResource = addOptionalQueryParam(webResource, "NextTableName", nextTableName); WebResource.Builder builder = webResource.getRequestBuilder(); builder = addTableRequestHeaders(builder); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java deleted file mode 100644 index 25f042b564f0f..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.microsoft.windowsazure.services.table.models; - -public class ListTablesOptions extends TableServiceOptions { - private String prefix; - - @Override - public ListTablesOptions setTimeout(Integer timeout) { - super.setTimeout(timeout); - return this; - } - - public String getPrefix() { - return prefix; - } - - public ListTablesOptions setPrefix(String prefix) { - this.prefix = prefix; - return this; - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index d402cd8527cce..b910ff6364d03 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -3,6 +3,7 @@ public class QueryTablesOptions extends TableServiceOptions { private String nextTableName; private Query query; + private String prefix; @Override public QueryTablesOptions setTimeout(Integer timeout) { @@ -27,4 +28,13 @@ public QueryTablesOptions setNextTableName(String nextTableName) { this.nextTableName = nextTableName; return this; } + + public String getPrefix() { + return prefix; + } + + public QueryTablesOptions setPrefix(String prefix) { + this.prefix = prefix; + return this; + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index c8143349741d4..4a4146a5055db 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -40,10 +40,10 @@ import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.ListTablesOptions; import com.microsoft.windowsazure.services.table.models.Query; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; +import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableEntry; @@ -117,7 +117,7 @@ private static void createTables(TableContract service, String prefix, String[] // Retry creating every table as long as we get "409 - Table being deleted" error service = service.withFilter(new RetryPolicyFilter(new ExponentialRetryPolicy(new int[] { 409 }))); - Set<String> containers = listTables(service, prefix); + Set<String> containers = queryTables(service, prefix); for (String item : list) { if (!containers.contains(item)) { service.createTable(item); @@ -126,7 +126,7 @@ private static void createTables(TableContract service, String prefix, String[] } private static void deleteTables(TableContract service, String prefix, String[] list) throws Exception { - Set<String> containers = listTables(service, prefix); + Set<String> containers = queryTables(service, prefix); for (String item : list) { if (containers.contains(item)) { service.deleteTable(item); @@ -145,9 +145,9 @@ private static void deleteAllTables(TableContract service, String[] list) throws } } - private static Set<String> listTables(TableContract service, String prefix) throws Exception { + private static Set<String> queryTables(TableContract service, String prefix) throws Exception { HashSet<String> result = new HashSet<String>(); - QueryTablesResult list = service.listTables(new ListTablesOptions().setPrefix(prefix)); + QueryTablesResult list = service.queryTables(new QueryTablesOptions().setPrefix(prefix)); for (TableEntry item : list.getTables()) { result.add(item.getName()); } @@ -267,19 +267,6 @@ public void queryTablesWorks() throws Exception { assertNotNull(result); } - @Test - public void listTablesWorks() throws Exception { - // Arrange - Configuration config = createConfiguration(); - TableContract service = TableService.create(config); - - // Act - QueryTablesResult result = service.listTables(); - - // Assert - assertNotNull(result); - } - @Test public void queryTablesWithPrefixWorks() throws Exception { // Arrange @@ -287,7 +274,7 @@ public void queryTablesWithPrefixWorks() throws Exception { TableContract service = TableService.create(config); // Act - QueryTablesResult result = service.listTables(new ListTablesOptions().setPrefix(testTablesPrefix)); + QueryTablesResult result = service.queryTables(new QueryTablesOptions().setPrefix(testTablesPrefix)); // Assert assertNotNull(result); From ff44325b9d916a6132bc46acd0b4ce1478f8707b Mon Sep 17 00:00:00 2001 From: Louis DeJardin <lodejard@microsoft.com> Date: Fri, 24 Feb 2012 14:20:39 -0800 Subject: [PATCH 25/76] Date time for table should have precision only down to seconds. Fixes #228 DateTime parser doesn't appear to round-trip accurately lower than that. --- .../implementation/ISO8601DateConverter.java | 4 ++++ .../table/implementation/AtomReaderWriter.java | 2 +- .../DefaultEdmValueConterter.java | 2 +- .../ISO8601DateConverterTests.java | 17 +++++++++++++++-- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index e796bf00794cc..9e6eef462738d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -33,6 +33,10 @@ public String format(Date date) { return getFormat().format(date); } + public String shortFormat(Date date) { + return getShortFormat().format(date); + } + public Date parse(String date) throws ParseException { if (date == null) return null; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index c5803bfb54111..75fdfe4c3a91c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -179,7 +179,7 @@ private InputStream generateEntry(PropertiesWriter propertiesWriter) { writer.writeEndElement(); // title writer.writeStartElement("updated"); - writer.writeCharacters(iso8601DateConverter.format(dateFactory.getDate())); + writer.writeCharacters(iso8601DateConverter.shortFormat(dateFactory.getDate())); writer.writeEndElement(); // updated writer.writeStartElement("author"); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index b062b3394ab9d..ac505af93f3d0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -25,7 +25,7 @@ public String serialize(String edmType, Object value) { String serializedValue; if (value instanceof Date) { - serializedValue = iso8601DateConverter.format((Date) value); + serializedValue = iso8601DateConverter.shortFormat((Date) value); } else { serializedValue = value.toString(); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java index 6f514dad26d3c..bb9e36e07d9c0 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java @@ -20,8 +20,6 @@ import org.junit.Test; -import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; - public class ISO8601DateConverterTests { @Test public void shortFormatWorks() throws Exception { @@ -48,4 +46,19 @@ public void longFormatWorks() throws Exception { // Assert assertNotNull(result); } + + @Test + public void shortFormatRoundTrips() throws Exception { + // Arrange + ISO8601DateConverter converter = new ISO8601DateConverter(); + String value = "2012-01-12T00:35:58Z"; + + // Act + Date result = converter.parse(value); + String value2 = converter.shortFormat(result); + + // Assert + assertNotNull(result); + assertEquals(value, value2); + } } From 46e59a37c7078cfbb51f0e1a8ace582045ae8eb3 Mon Sep 17 00:00:00 2001 From: Louis DeJardin <lodejard@microsoft.com> Date: Fri, 24 Feb 2012 14:25:55 -0800 Subject: [PATCH 26/76] Use X-HTTP-Method for MERGE verb. Fixes #234 Java http libraries assert on known set of verbs --- .../services/table/implementation/TableRestProxy.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 096c5b4ec312e..284fbe475ff7c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -466,6 +466,10 @@ private UpdateEntityResult putOrMergeEntityCore(String table, Entity entity, Str if (includeEtag) { builder = addIfMatchHeader(builder, entity.getEtag()); } + if (verb == "MERGE") { + builder = builder.header("X-HTTP-Method", "MERGE"); + verb = "POST"; + } builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); From 2115c8e33a50d951163bf8da2038fad1431585da Mon Sep 17 00:00:00 2001 From: Louis DeJardin <lodejard@microsoft.com> Date: Fri, 24 Feb 2012 14:42:41 -0800 Subject: [PATCH 27/76] Bumping version to 0.2.0 --- microsoft-azure-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index d9b0c4c4f2cf0..c83d5dcc1a333 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -3,7 +3,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.microsoft.windowsazure</groupId> <artifactId>microsoft-windowsazure-api</artifactId> - <version>0.1.3</version> + <version>0.2.0</version> <packaging>jar</packaging> <name>Microsoft Windows Azure Client API</name> From 4af2c067eeb7436768b94c2505d44665bead743e Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Mon, 27 Feb 2012 13:46:32 -0800 Subject: [PATCH 28/76] Adding copyright and license information to new table files. --- .../services/table/EdmValueConverter.java | 14 ++++++++++++++ .../windowsazure/services/table/Exports.java | 2 +- .../services/table/TableConfiguration.java | 2 +- .../services/table/TableContract.java | 2 +- .../services/table/TableService.java | 18 +++++++++--------- .../table/implementation/AtomReaderWriter.java | 14 ++++++++++++++ .../DefaultEdmValueConterter.java | 14 ++++++++++++++ .../DefaultXMLStreamFactory.java | 14 ++++++++++++++ .../table/implementation/HttpReaderWriter.java | 14 ++++++++++++++ .../implementation/InputStreamDataSource.java | 14 ++++++++++++++ .../table/implementation/MimeReaderWriter.java | 14 ++++++++++++++ .../table/implementation/SharedKeyFilter.java | 2 +- .../implementation/SharedKeyLiteFilter.java | 2 +- .../TableExceptionProcessor.java | 2 +- .../table/implementation/TableRestProxy.java | 2 +- .../table/implementation/XMLStreamFactory.java | 14 ++++++++++++++ .../services/table/models/BatchOperations.java | 14 ++++++++++++++ .../services/table/models/BatchResult.java | 14 ++++++++++++++ .../services/table/models/BinaryFilter.java | 14 ++++++++++++++ .../services/table/models/ConstantFilter.java | 14 ++++++++++++++ .../table/models/DeleteEntityOptions.java | 14 ++++++++++++++ .../services/table/models/EdmType.java | 14 ++++++++++++++ .../services/table/models/Entity.java | 14 ++++++++++++++ .../services/table/models/Filter.java | 14 ++++++++++++++ .../services/table/models/GetEntityResult.java | 14 ++++++++++++++ .../models/GetServicePropertiesResult.java | 18 +++++++++--------- .../services/table/models/GetTableResult.java | 14 ++++++++++++++ .../table/models/InsertEntityResult.java | 14 ++++++++++++++ .../services/table/models/LitteralFilter.java | 14 ++++++++++++++ .../services/table/models/Property.java | 14 ++++++++++++++ .../services/table/models/Query.java | 14 ++++++++++++++ .../table/models/QueryEntitiesOptions.java | 14 ++++++++++++++ .../table/models/QueryEntitiesResult.java | 14 ++++++++++++++ .../table/models/QueryTablesOptions.java | 14 ++++++++++++++ .../table/models/QueryTablesResult.java | 14 ++++++++++++++ .../services/table/models/RawStringFilter.java | 14 ++++++++++++++ .../table/models/ServiceProperties.java | 18 +++++++++--------- .../services/table/models/TableEntry.java | 14 ++++++++++++++ .../table/models/TableServiceOptions.java | 18 +++++++++--------- .../services/table/models/UnaryFilter.java | 14 ++++++++++++++ .../table/models/UpdateEntityResult.java | 14 ++++++++++++++ 41 files changed, 463 insertions(+), 43 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java index e7582255ef590..4a6fd5fb37fbc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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; public interface EdmValueConverter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java index ee52dd47bfb29..ad0334ab03de4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java index 49c90e0ddf3d2..33391fc9e711f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 233f3d8001405..2bb4e1a3ac6ae 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java index 2e732e4fc1d6e..c37f20da147f8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java @@ -1,16 +1,16 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 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 + * 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. + * 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; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index 75fdfe4c3a91c..e7b067f5d0ad0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.ByteArrayInputStream; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index ac505af93f3d0..f7df91f533f51 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.text.ParseException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java index 8504c88fa349f..6202250942d4f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.InputStream; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java index 458acf6e0cd96..a0120eb419846 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.IOException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java index 22c099a52fee3..a15d80844155d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.IOException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java index 9d2282ab08e68..30aa13ec6bfe8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.IOException; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java index 79305ce758c84..ba12447147c89 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java index 094fe429145a7..6f9990616a822 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java index 8f4bc110d4da5..29e8bd6dd8129 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 80f16b99187be..bfeddfdb5c807 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 Microsoft Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java index 0e97c0193ab5b..064becc8b8650 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.implementation; import java.io.InputStream; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index f1df873ac4da0..5de01c01542a8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.ArrayList; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java index 30a0b3beebd94..6ca4400068bb0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.ArrayList; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java index 462be9a865c63..2cdb8473a26d3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class BinaryFilter extends Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java index 9dd16b1a1daa8..098b28ae0e397 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class ConstantFilter extends Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java index 441f3020822b4..161087d46834a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class DeleteEntityOptions extends TableServiceOptions { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java index 5a82435b93375..0f0585f2e22a9 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class EdmType { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java index ce1d6dff3933c..f74bda6d7003f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.Date; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index f0a54f58b09b7..31a6daf581b22 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java index 34195d4795047..336c436da06c8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class GetEntityResult { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java index 30ab7196d4489..e56a2c8f9368d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java @@ -1,16 +1,16 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 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 + * 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. + * 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.models; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java index de199b24f075f..0e23a0d752c20 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class GetTableResult { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java index 666fc9634da3e..eca0203954967 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class InsertEntityResult { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java index d67f47162c4d6..08a62dbf957b5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class LitteralFilter extends Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java index 1e90530e04f06..197584f920e6d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class Property { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java index 57e7f8c30b660..cf3997dd7225c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.ArrayList; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index 1de1cd47426b5..fe6842b62082d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class QueryEntitiesOptions extends TableServiceOptions { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java index 3977ccc9b97d4..9756661eef96b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.ArrayList; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index b910ff6364d03..13cc7fcf70024 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class QueryTablesOptions extends TableServiceOptions { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java index dfd1c1b7f4aae..9c6158382dba3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; import java.util.List; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java index ffc8e38aef00c..12aaedf407d4e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class RawStringFilter extends Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java index 2e068e4fe9dff..73bffc9e03b67 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java @@ -1,16 +1,16 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 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 + * 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. + * 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.models; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java index 917cecc3409c5..2e520bc36db17 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class TableEntry { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java index e42240885c15b..69c19ecba5547 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java @@ -1,16 +1,16 @@ /** - * Copyright 2011 Microsoft Corporation + * Copyright 2012 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 + * 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. + * 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.models; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java index 42d4b7a870fc4..f6da13830a0cb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class UnaryFilter extends Filter { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java index 2db782e636934..dc18aa98996ec 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.models; public class UpdateEntityResult { From 7cd69630d5e3b2f829e7bafa7322a041201fdfeb Mon Sep 17 00:00:00 2001 From: Joost de Nijs <joostden@microsoft.com> Date: Tue, 28 Feb 2012 14:34:33 -0800 Subject: [PATCH 29/76] Update ChangeLog.txt --- ChangeLog.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index f42d57277e0f6..8cb5a1fec8e99 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,4 +1,10 @@ -2012.01.31. Version 0.1.3 +2012.02.28 Version 0.2.0 + * Added Support for Azure Table in com.microsoft.windowsazure.services.table + * Added Tests for Azure Table + * Added a dependency on apache commons-lang3 3.1 + * UserAgent updated to v1.1.2 + +2012.01.31 Version 0.1.3 * Updated User Agent to v0.1.1 * Updated License Headers * Blob Client Mark bug fix @@ -8,7 +14,7 @@ * Date parsing support for various number of fractional decimals * StorageErrorResponse updated to support lower case xml for tables -2011.12.22. Version 0.1.2 +2011.12.22 Version 0.1.2 * Fixed CloudBlob.download to lock to ETag during a resume * Ensured that Client Side Exceptions are not resumed From fd92be1a9c6bf28f87294e1312834b37c22a6c87 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:07:08 -0800 Subject: [PATCH 30/76] Fix issue 198 --- .../services/table/models/ServiceProperties.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java index 73bffc9e03b67..01a18543864b1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java @@ -42,9 +42,9 @@ public void setMetrics(Metrics metrics) { public static class Logging { private String version; - private Boolean delete; - private Boolean read; - private Boolean write; + private boolean delete; + private boolean read; + private boolean write; private RetentionPolicy retentionPolicy; @XmlElement(name = "RetentionPolicy") From f239a502e3bf9b9d78dbee3812144de2b81c56a8 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:14:53 -0800 Subject: [PATCH 31/76] Fix #227, #244, and #251 --- .../table/implementation/AtomReaderWriter.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index e7b067f5d0ad0..906c4bdc4a757 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -80,6 +80,9 @@ public void write(XMLStreamWriter writer) throws XMLStreamException { if (value != null) { writer.writeCharacters(value); } + else { + writer.writeAttribute("m:null", "true"); + } writer.writeEndElement(); // property name @@ -276,12 +279,21 @@ private Map<String, Property> parseEntryProperties(XMLStreamReader xmlr) throws String edmType = xmlr.getAttributeValue(null, "type"); xmlr.next(); - String serializedValue = xmlr.getText(); + + // Use concatenation instead of StringBuilder as most text is just one element. + String serializedValue = ""; + while (!xmlr.isEndElement()) { + serializedValue += xmlr.getText(); + xmlr.next(); + } + Object value = edmValueConverter.deserialize(edmType, serializedValue); result.put(name, new Property().setEdmType(edmType).setValue(value)); - nextSignificant(xmlr); + if (!xmlr.isEndElement()) { + nextSignificant(xmlr); + } expect(xmlr, XMLStreamConstants.END_ELEMENT, name); } From 37e8b0f9ca6e7561c74aeb81dce0e49b1fc22289 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:19:17 -0800 Subject: [PATCH 32/76] Fix #225 --- .../table/implementation/DefaultEdmValueConterter.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index f7df91f533f51..41b38735afa51 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -22,6 +22,7 @@ import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.table.EdmValueConverter; import com.microsoft.windowsazure.services.table.models.EdmType; +import com.sun.jersey.core.util.Base64; public class DefaultEdmValueConterter implements EdmValueConverter { @@ -41,6 +42,9 @@ public String serialize(String edmType, Object value) { if (value instanceof Date) { serializedValue = iso8601DateConverter.shortFormat((Date) value); } + else if (value instanceof byte[]) { + serializedValue = new String(Base64.encode((byte[]) value)); + } else { serializedValue = value.toString(); } @@ -73,6 +77,9 @@ else if (EdmType.INT32.equals(edmType)) { else if (EdmType.INT64.equals(edmType)) { return Long.parseLong(value); } + else if (EdmType.BINARY.equals(edmType)) { + return Base64.decode(value); + } return value; } From 960abc52657ddc33dca10f6e38a019721eae2d38 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:22:38 -0800 Subject: [PATCH 33/76] Fix #252 --- .../table/implementation/SharedKeyFilter.java | 2 +- .../services/table/implementation/TableRestProxy.java | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java index ba12447147c89..7e2e95bb88f9a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -77,7 +77,7 @@ public void sign(ClientRequest cr) { private String getCanonicalizedResource(ClientRequest cr) { String result = "/" + this.getAccountName(); - result += cr.getURI().getPath(); + result += cr.getURI().getRawPath(); List<QueryParam> queryParams = SharedKeyUtils.getQueryParams(cr.getURI().getQuery()); for (QueryParam p : queryParams) { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..e9a5dbc846493 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -159,7 +159,16 @@ private List<String> encodeODataURIValues(List<String> values) { } private String getEntityPath(String table, String partitionKey, String rowKey) { - return table + "(" + "PartitionKey='" + partitionKey + "',RowKey='" + rowKey + "')"; + String ret = "error"; + try { + ret = table + "(" + "PartitionKey='" + + URLEncoder.encode(partitionKey, "UTF-8").replace("+", "%20").replace("'", "%27") + "',RowKey='" + + URLEncoder.encode(rowKey, "UTF-8").replace("+", "%20").replace("'", "%27") + "')"; + System.out.println("ret : " + ret); + } + catch (UnsupportedEncodingException e) { + } + return ret; } private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { From 4ac38a8b412eddb415a295d6121ba43df6fbd1ee Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:28:28 -0800 Subject: [PATCH 34/76] Fix #205 --- .../queue/implementation/QueueRestProxy.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java index 4677d0431b818..5816a1bec264d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java @@ -152,6 +152,9 @@ public void createQueue(String queue) throws ServiceException { } public void createQueue(String queue, CreateQueueOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue); WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); @@ -165,6 +168,9 @@ public void deleteQueue(String queue) throws ServiceException { } public void deleteQueue(String queue, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue); WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); @@ -195,6 +201,9 @@ public GetQueueMetadataResult getQueueMetadata(String queue) throws ServiceExcep } public GetQueueMetadataResult getQueueMetadata(String queue, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).queryParam("comp", "metadata"); Builder builder = webResource.header("x-ms-version", API_VERSION); @@ -216,6 +225,9 @@ public void setQueueMetadata(String queue, HashMap<String, String> metadata) thr public void setQueueMetadata(String queue, HashMap<String, String> metadata, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).queryParam("comp", "metadata"); WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION); @@ -229,6 +241,9 @@ public void createMessage(String queue, String messageText) throws ServiceExcept } public void createMessage(String queue, String messageText, CreateMessageOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages"); webResource = addOptionalQueryParam(webResource, "visibilitytimeout", options.getVisibilityTimeoutInSeconds()); webResource = addOptionalQueryParam(webResource, "messagettl", options.getTimeToLiveInSeconds()); @@ -249,6 +264,11 @@ public UpdateMessageResult updateMessage(String queue, String messageId, String public UpdateMessageResult updateMessage(String queue, String messageId, String popReceipt, String messageText, int visibilityTimeoutInSeconds, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + if (messageId == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages").path(messageId); webResource = addOptionalQueryParam(webResource, "popreceipt", popReceipt); webResource = addOptionalQueryParam(webResource, "visibilitytimeout", visibilityTimeoutInSeconds); @@ -272,6 +292,9 @@ public ListMessagesResult listMessages(String queue) throws ServiceException { } public ListMessagesResult listMessages(String queue, ListMessagesOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages"); webResource = addOptionalQueryParam(webResource, "visibilitytimeout", options.getVisibilityTimeoutInSeconds()); webResource = addOptionalQueryParam(webResource, "numofmessages", options.getNumberOfMessages()); @@ -286,6 +309,9 @@ public PeekMessagesResult peekMessages(String queue) throws ServiceException { } public PeekMessagesResult peekMessages(String queue, PeekMessagesOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages").queryParam("peekonly", "true"); webResource = addOptionalQueryParam(webResource, "numofmessages", options.getNumberOfMessages()); @@ -300,6 +326,11 @@ public void deleteMessage(String queue, String messageId, String popReceipt) thr public void deleteMessage(String queue, String messageId, String popReceipt, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + if (messageId == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages").path(messageId); webResource = addOptionalQueryParam(webResource, "popreceipt", popReceipt); @@ -313,6 +344,9 @@ public void clearMessages(String queue) throws ServiceException { } public void clearMessages(String queue, QueueServiceOptions options) throws ServiceException { + if (queue == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(queue).path("messages"); Builder builder = webResource.header("x-ms-version", API_VERSION); From c372a0081f528a21f957c16337771a30d1276259 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:32:32 -0800 Subject: [PATCH 35/76] Fix #257 --- .../windowsazure/services/core/utils/pipeline/Exports.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java index 234581b023fb8..f7aae2cd9188f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java @@ -22,6 +22,7 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.api.client.filter.LoggingFilter; public class Exports implements Builder.Exports { @@ -52,7 +53,7 @@ public Client create(String profile, Builder builder, Map<String, Object> proper public HttpURLConnectionClient create(String profile, Builder builder, Map<String, Object> properties) { ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig"); HttpURLConnectionClient client = HttpURLConnectionClient.create(clientConfig); - //client.addFilter(new LoggingFilter()); + client.addFilter(new LoggingFilter()); return client; } }); From 2b3d4bf8bf76739dcc9d7267dfe877e981657a78 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:39:16 -0800 Subject: [PATCH 36/76] Fix #228 --- .../implementation/ISO8601DateConverter.java | 59 ++++++++++++------- .../implementation/AtomReaderWriter.java | 2 +- .../DefaultEdmValueConterter.java | 2 +- .../ISO8601DateConverterTests.java | 2 +- 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 9e6eef462738d..9fa415c44c8f1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -17,6 +17,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; import java.util.Locale; import java.util.TimeZone; @@ -26,38 +27,54 @@ */ public class ISO8601DateConverter { // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible - private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'"; - private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String DATETIME_PATTERN_NO_S = "yyyy-MM-dd'T'HH:mm'Z'"; + private static final String DATETIME_PATTERN_NO_MS = "yyyy-MM-dd'T'HH:mm:ss'Z'"; + private static final String DATETIME_PATTERN_TO_DECIMAL = "yyyy-MM-dd'T'HH:mm:ss."; public String format(Date date) { - return getFormat().format(date); - } - - public String shortFormat(Date date) { - return getShortFormat().format(date); + DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); + return iso8601Format.format(date); } public Date parse(String date) throws ParseException { if (date == null) return null; - // Sometimes, the date comes back without the ".SSSSSSS" part (presumably when the decimal value - // of the date is "0". Use the short format in that case. - if (date.indexOf('.') < 0) - return getShortFormat().parse(date); - else - return getFormat().parse(date); - } + int length = date.length(); + if (length == 17) { + // [2012-01-04T23:21Z] length = 17 + return parseDateFromString(date, DATETIME_PATTERN_NO_S); + } + else if (length == 20) { + // [2012-01-04T23:21:59Z] length = 20 + return parseDateFromString(date, DATETIME_PATTERN_NO_MS); + } + else if (length >= 22 && length <= 28) { + // [2012-01-04T23:21:59.1Z] length = 22 + // [2012-01-04T23:21:59.1234567Z] length = 28 + // Need to handle the milliseconds gently. - private DateFormat getFormat() { - DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); - iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); - return iso8601Format; + Date allExceptMilliseconds = parseDateFromString(date, DATETIME_PATTERN_TO_DECIMAL); + long timeWithSecondGranularity = allExceptMilliseconds.getTime(); + // Decimal point is at 19 + String secondDecimalString = date.substring(19, date.indexOf('Z')); + Float secondDecimal = Float.parseFloat(secondDecimalString); + int milliseconds = Math.round(secondDecimal * 1000); + long timeInMS = timeWithSecondGranularity + milliseconds; + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(timeInMS); + return cal.getTime(); + } + else { + throw new IllegalArgumentException(String.format("Invalid Date String: %s", date)); + } } - private DateFormat getShortFormat() { - DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + public static Date parseDateFromString(final String value, final String pattern) throws ParseException { + DateFormat iso8601Format = new SimpleDateFormat(pattern, Locale.US); iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); - return iso8601Format; + return iso8601Format.parse(value); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index e7b067f5d0ad0..3324a62d68c55 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -193,7 +193,7 @@ private InputStream generateEntry(PropertiesWriter propertiesWriter) { writer.writeEndElement(); // title writer.writeStartElement("updated"); - writer.writeCharacters(iso8601DateConverter.shortFormat(dateFactory.getDate())); + writer.writeCharacters(iso8601DateConverter.format(dateFactory.getDate())); writer.writeEndElement(); // updated writer.writeStartElement("author"); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index f7df91f533f51..a9ec25452eea8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -39,7 +39,7 @@ public String serialize(String edmType, Object value) { String serializedValue; if (value instanceof Date) { - serializedValue = iso8601DateConverter.shortFormat((Date) value); + serializedValue = iso8601DateConverter.format((Date) value); } else { serializedValue = value.toString(); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java index bb9e36e07d9c0..a777b001c5841 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java @@ -55,7 +55,7 @@ public void shortFormatRoundTrips() throws Exception { // Act Date result = converter.parse(value); - String value2 = converter.shortFormat(result); + String value2 = converter.format(result); // Assert assertNotNull(result); From 3bf1d0940c3fce030a865edc3c99bdb5bb965a3f Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:46:31 -0800 Subject: [PATCH 37/76] Fix #221 and #231 --- .../table/implementation/TableRestProxy.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..fb22323fa3573 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -282,6 +282,9 @@ public void setServiceProperties(ServiceProperties serviceProperties) throws Ser @Override public void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException { + if (serviceProperties == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path("/").queryParam("resType", "service") .queryParam("comp", "properties"); @@ -297,6 +300,9 @@ public GetTableResult getTable(String table) throws ServiceException { @Override public GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path("Tables" + "('" + table + "')"); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -369,6 +375,9 @@ public void createTable(String table) throws ServiceException { @Override public void createTable(String table, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path("Tables"); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -387,6 +396,9 @@ public void deleteTable(String table) throws ServiceException { @Override public void deleteTable(String table, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path("Tables" + "('" + table + "')"); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -405,6 +417,9 @@ public InsertEntityResult insertEntity(String table, Entity entity) throws Servi @Override public InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(table); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -434,7 +449,7 @@ public UpdateEntityResult updateEntity(String table, Entity entity, TableService @Override public UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException { - return updateEntity(table, entity, new TableServiceOptions()); + return mergeEntity(table, entity, new TableServiceOptions()); } @Override @@ -456,7 +471,7 @@ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, Tab @Override public UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException { - return insertOrReplaceEntity(table, entity, new TableServiceOptions()); + return insertOrMergeEntity(table, entity, new TableServiceOptions()); } @Override @@ -467,6 +482,9 @@ public UpdateEntityResult insertOrMergeEntity(String table, Entity entity, Table private UpdateEntityResult putOrMergeEntityCore(String table, Entity entity, String verb, boolean includeEtag, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path( getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())); @@ -499,6 +517,9 @@ public void deleteEntity(String table, String partitionKey, String rowKey) throw @Override public void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey)); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -517,6 +538,9 @@ public GetEntityResult getEntity(String table, String partitionKey, String rowKe @Override public GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey)); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -538,6 +562,11 @@ public QueryEntitiesResult queryEntities(String table) throws ServiceException { @Override public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException { + if (table == null) + throw new NullPointerException(); + if (options == null) + options = new QueryEntitiesOptions(); + WebResource webResource = getResource(options).path(table); webResource = addOptionalQuery(webResource, options.getQuery()); webResource = addOptionalQueryParam(webResource, "NextPartitionKey", From 62435263d18dd3ca63f2293ca3abb9c84fe73045 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 10:54:15 -0800 Subject: [PATCH 38/76] Fix #213 --- .../table/implementation/TableRestProxy.java | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..c65db1d6bbf06 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -205,9 +205,47 @@ private void buildFilterExpression(Filter filter, StringBuilder sb) { sb.append(((LitteralFilter) filter).getLitteral()); } else if (filter instanceof ConstantFilter) { - sb.append("'"); - sb.append(((ConstantFilter) filter).getValue()); - sb.append("'"); + // Look up http://www.odata.org/developers/protocols/overview + Object value = ((ConstantFilter) filter).getValue(); + if (value == null) { + sb.append("null"); + } + else if (value.getClass() == Long.class) { + sb.append(value + "L"); + } + else if (value.getClass() == Date.class) { + ISO8601DateConverter dc = new ISO8601DateConverter(); + sb.append("datetime'"); + sb.append(dc.format((Date) value)); + sb.append("'"); + } + else if (value.getClass() == String.class) { + // Need to special case guids, which argues for using UUID. + try { + UUID.fromString((String) value); + // Looks like guid + sb.append("(guid'" + value + "')"); + } + catch (Exception ex) { + // Not guid + sb.append("'"); + sb.append(((String) value).replace("'", "''")); + sb.append("'"); + + } + } + else if (value.getClass() == byte[].class) { + byte[] x = (byte[]) value; + sb.append("binary'"); + for (int j = 0; j < x.length; j++) { + sb.append(BASE_16_CHARS.charAt((x[j] & 0xF0) >>> 4)); + sb.append(BASE_16_CHARS.charAt((x[j] & 0x0F))); + } + sb.append("'"); + } + else { + sb.append(value); + } } else if (filter instanceof UnaryFilter) { sb.append(((UnaryFilter) filter).getOperator()); From 0078bd42acfa70803c3ba71799175afe21137dc5 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 11:23:29 -0800 Subject: [PATCH 39/76] Fix for #243, #245, and #254 --- .../table/implementation/TableRestProxy.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..af01e789b06e6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import javax.activation.DataSource; @@ -30,6 +31,9 @@ import javax.mail.Header; import javax.mail.internet.InternetHeaders; import javax.mail.internet.MimeMultipart; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter; import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; @@ -83,6 +87,10 @@ import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; import com.sun.jersey.core.header.InBoundHeaders; +import com.sun.jersey.core.spi.component.ProviderFactory; +import com.sun.jersey.core.spi.component.ProviderServices; +import com.sun.jersey.core.spi.factory.MessageBodyFactory; +import com.sun.jersey.spi.inject.ClientSide; public class TableRestProxy implements TableContract { private static final String API_VERSION = "2011-08-18"; @@ -700,14 +708,7 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); - if (parts.size() != operations.getOperations().size()) { - throw new UniformInterfaceException(String.format( - "Batch response from server does not contain the correct amount " - + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations() - .size()), response); - } - - List<Entry> result = new ArrayList<Entry>(); + Entry[] entries = new Entry[operations.getOperations().size()]; for (int i = 0; i < parts.size(); i++) { DataSource ds = parts.get(i); Operation operation = operations.getOperations().get(i); @@ -726,7 +727,13 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations inBoundHeaders.putSingle(header.getName(), header.getValue()); } - ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, null); + ProviderServices providerServices = new ProviderServices(ClientSide.class, new ProviderFactory(null), + new HashSet<Class<?>>(), new HashSet<Class<?>>()); + MessageBodyFactory bodyContext = new MessageBodyFactory(providerServices, false); + // TODO: This call causes lots of warnings from Jersey. Need to figure out how to silence them. + bodyContext.init(); + ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, + bodyContext); // Wrap into a ServiceException UniformInterfaceException exception = new UniformInterfaceException(dummyResponse); @@ -734,24 +741,51 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations serviceException = ServiceExceptionFactory.process("table", serviceException); Error error = new Error().setError(serviceException); - result.add(error); + // Parse the message to find which operation caused this error. + try { + XMLInputFactory xmlStreamFactory = XMLInputFactory.newFactory(); + XMLStreamReader xmlr = xmlStreamFactory.createXMLStreamReader(new ByteArrayInputStream( + serviceException.getRawResponseBody().getBytes())); + while (xmlr.hasNext()) { + xmlr.next(); + if (xmlr.isStartElement() && "message".equals(xmlr.getLocalName())) { + xmlr.next(); + // Process "message" elements only + String message = xmlr.getText(); + int colonIndex = message.indexOf(':'); + String errorOpId = message.substring(0, colonIndex); + int opId = Integer.parseInt(errorOpId); + entries[opId] = error; + } + } + xmlr.close(); + } + catch (XMLStreamException e1) { + // TODO: What to throw here? + } + } else if (operation instanceof InsertEntityOperation) { InsertEntity opResult = new InsertEntity().setEntity(atomReaderWriter.parseEntityEntry(content)); - result.add(opResult); + entries[i] = opResult; } else if ((operation instanceof UpdateEntityOperation) || (operation instanceof MergeEntityOperation) || (operation instanceof InsertOrReplaceEntityOperation) || (operation instanceof InsertOrMergeEntityOperation)) { UpdateEntity opResult = new UpdateEntity().setEtag(headers.getHeader("ETag", null)); - result.add(opResult); + entries[i] = opResult; } else if (operation instanceof DeleteEntityOperation) { DeleteEntity opResult = new DeleteEntity(); - result.add(opResult); + entries[i] = opResult; } } + List<Entry> result = new ArrayList<Entry>(); + for (int i = 0; i < entries.length; i++) { + result.add(entries[i]); + } + return result; } From b329c8941e43a28a39f9776ff1a4835d3d262566 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 8 Mar 2012 11:33:01 -0800 Subject: [PATCH 40/76] Fix #209 --- .../services/table/implementation/TableRestProxy.java | 1 - .../services/table/models/DeleteEntityOptions.java | 6 ------ .../services/table/models/QueryEntitiesOptions.java | 6 ------ .../services/table/models/QueryTablesOptions.java | 6 ------ .../services/table/models/TableServiceOptions.java | 11 ----------- 5 files changed, 30 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..11c59390988fa 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -249,7 +249,6 @@ private WebResource.Builder addIfMatchHeader(WebResource.Builder builder, String private WebResource getResource(TableServiceOptions options) { WebResource webResource = channel.resource(url).path("/"); - webResource = addOptionalQueryParam(webResource, "timeout", options.getTimeout()); for (ServiceFilter filter : filters) { webResource.addFilter(new ClientFilterAdapter(filter)); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java index 161087d46834a..4cce1e14a24a9 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java @@ -17,12 +17,6 @@ public class DeleteEntityOptions extends TableServiceOptions { private String etag; - @Override - public DeleteEntityOptions setTimeout(Integer timeout) { - super.setTimeout(timeout); - return this; - } - public String getEtag() { return etag; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index fe6842b62082d..31c7d05652bc7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -19,12 +19,6 @@ public class QueryEntitiesOptions extends TableServiceOptions { public String nextPartitionKey; public String nextRowKey; - @Override - public QueryEntitiesOptions setTimeout(Integer timeout) { - super.setTimeout(timeout); - return this; - } - public Query getQuery() { return query; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index 13cc7fcf70024..36b975d7a6900 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -19,12 +19,6 @@ public class QueryTablesOptions extends TableServiceOptions { private Query query; private String prefix; - @Override - public QueryTablesOptions setTimeout(Integer timeout) { - super.setTimeout(timeout); - return this; - } - public Query getQuery() { return query; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java index 69c19ecba5547..c4a733a6e3a6b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java @@ -15,15 +15,4 @@ package com.microsoft.windowsazure.services.table.models; public class TableServiceOptions { - // Nullable because it is optional - private Integer timeout; - - public Integer getTimeout() { - return timeout; - } - - public TableServiceOptions setTimeout(Integer timeout) { - this.timeout = timeout; - return this; - } } From 1782b8445e1463fb6c1e4f768c9475b45c2e9c68 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 29 Mar 2012 13:27:35 -0700 Subject: [PATCH 41/76] Make fix suggested in code review feedback. --- .../services/table/implementation/AtomReaderWriter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java index 906c4bdc4a757..525c2adf086ae 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -291,9 +291,6 @@ private Map<String, Property> parseEntryProperties(XMLStreamReader xmlr) throws result.put(name, new Property().setEdmType(edmType).setValue(value)); - if (!xmlr.isEndElement()) { - nextSignificant(xmlr); - } expect(xmlr, XMLStreamConstants.END_ELEMENT, name); } From 84bcfaf6159091bf6ce1a1b4d9a351d6e9d9a362 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 08:09:51 -0700 Subject: [PATCH 42/76] Update unit tests to cover EDM BINARY type. --- .../table/TableServiceIntegrationTest.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 4a4146a5055db..5f14d2d82a1f7 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -300,10 +300,11 @@ public void insertEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + byte[] binaryData = new byte[] { 1, 2, 3, 4 }; Entity entity = new Entity().setPartitionKey("001").setRowKey("insertEntityWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date()); + .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData); // Act InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); @@ -331,6 +332,14 @@ public void insertEntityWorks() throws Exception { assertNotNull(result.getEntity().getProperty("test5")); assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); + + assertNotNull(result.getEntity().getProperty("test6")); + assertTrue(result.getEntity().getProperty("test6").getValue() instanceof byte[]); + byte[] returnedBinaryData = (byte[]) result.getEntity().getProperty("test6").getValue(); + assertEquals(binaryData.length, returnedBinaryData.length); + for (int i = 0; i < binaryData.length; i++) { + assertEquals(binaryData[i], returnedBinaryData[i]); + } } @Test @@ -465,10 +474,11 @@ public void getEntityWorks() throws Exception { // Arrange Configuration config = createConfiguration(); TableContract service = TableService.create(config); + byte[] binaryData = new byte[] { 1, 2, 3, 4 }; Entity entity = new Entity().setPartitionKey("001").setRowKey("getEntityWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date()); + .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData); // Act InsertEntityResult insertResult = service.insertEntity(TEST_TABLE_2, entity); @@ -498,6 +508,14 @@ public void getEntityWorks() throws Exception { assertNotNull(result.getEntity().getProperty("test5")); assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date); + + assertNotNull(result.getEntity().getProperty("test6")); + assertTrue(result.getEntity().getProperty("test6").getValue() instanceof byte[]); + byte[] returnedBinaryData = (byte[]) result.getEntity().getProperty("test6").getValue(); + assertEquals(binaryData.length, returnedBinaryData.length); + for (int i = 0; i < binaryData.length; i++) { + assertEquals(binaryData[i], returnedBinaryData[i]); + } } @Test From 6a569ecf939555a9dee642bc15fbe3eba540f437 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 10:53:51 -0700 Subject: [PATCH 43/76] Replaced manual base16 conversion with Formatter call. Use existing StringBuilder instead of concat when possible. Add case for Byte[] (in addition to byte[]) Replace special case in String for UUID with seperate code path for UUID. Add unit tests. --- .../table/implementation/TableRestProxy.java | 51 +++++++------ .../table/TableServiceIntegrationTest.java | 72 +++++++++++++++++-- 2 files changed, 95 insertions(+), 28 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index c65db1d6bbf06..5b7875c0ab983 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -21,8 +21,11 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Date; import java.util.Enumeration; +import java.util.Formatter; import java.util.List; +import java.util.UUID; import javax.activation.DataSource; import javax.inject.Inject; @@ -205,41 +208,45 @@ private void buildFilterExpression(Filter filter, StringBuilder sb) { sb.append(((LitteralFilter) filter).getLitteral()); } else if (filter instanceof ConstantFilter) { - // Look up http://www.odata.org/developers/protocols/overview Object value = ((ConstantFilter) filter).getValue(); if (value == null) { sb.append("null"); } else if (value.getClass() == Long.class) { - sb.append(value + "L"); + sb.append(value); + sb.append("L"); } else if (value.getClass() == Date.class) { - ISO8601DateConverter dc = new ISO8601DateConverter(); + ISO8601DateConverter dateConverter = new ISO8601DateConverter(); sb.append("datetime'"); - sb.append(dc.format((Date) value)); + sb.append(dateConverter.format((Date) value)); sb.append("'"); } + else if (value.getClass() == UUID.class) { + sb.append("(guid'"); + sb.append(value); + sb.append("')"); + } else if (value.getClass() == String.class) { - // Need to special case guids, which argues for using UUID. - try { - UUID.fromString((String) value); - // Looks like guid - sb.append("(guid'" + value + "')"); - } - catch (Exception ex) { - // Not guid - sb.append("'"); - sb.append(((String) value).replace("'", "''")); - sb.append("'"); - - } + sb.append("'"); + sb.append(((String) value).replace("'", "''")); + sb.append("'"); } else if (value.getClass() == byte[].class) { - byte[] x = (byte[]) value; - sb.append("binary'"); - for (int j = 0; j < x.length; j++) { - sb.append(BASE_16_CHARS.charAt((x[j] & 0xF0) >>> 4)); - sb.append(BASE_16_CHARS.charAt((x[j] & 0x0F))); + sb.append("X'"); + byte[] byteArray = (byte[]) value; + Formatter formatter = new Formatter(sb); + for (byte b : byteArray) { + formatter.format("%02x", b); + } + sb.append("'"); + } + else if (value.getClass() == Byte[].class) { + sb.append("X'"); + Byte[] byteArray = (Byte[]) value; + Formatter formatter = new Formatter(sb); + for (Byte b : byteArray) { + formatter.format("%02x", b); } sb.append("'"); } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 4a4146a5055db..d5d2ceafc8dec 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -592,13 +592,16 @@ public void queryEntitiesWithFilterWorks() throws Exception { TableContract service = TableService.create(config); String table = TEST_TABLE_5; int numberOfEntries = 5; + Entity[] entities = new Entity[numberOfEntries]; for (int i = 0; i < numberOfEntries; i++) { - Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithFilterWorks-" + i) - .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") - .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date()); - - service.insertEntity(table, entity); + entities[i] = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithFilterWorks-" + i) + .setProperty("test", EdmType.BOOLEAN, (i % 2 == 0)) + .setProperty("test2", EdmType.STRING, "'value'" + i).setProperty("test3", EdmType.INT32, i) + .setProperty("test4", EdmType.INT64, 12345678901L + i) + .setProperty("test5", EdmType.DATETIME, new Date(i * 1000)) + .setProperty("test6", EdmType.GUID, UUID.randomUUID()); + + service.insertEntity(table, entities[i]); } { @@ -623,6 +626,63 @@ public void queryEntitiesWithFilterWorks() throws Exception { assertEquals(1, result.getEntities().size()); assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); } + + { + // Act + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("test"), + Filter.constant(true))))); + + // Assert + assertNotNull(result); + assertEquals(3, result.getEntities().size()); + } + + { + // Act + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() + .setFilter(Filter.eq(Filter.litteral("test2"), Filter.constant("'value'3"))))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } + + { + // Act + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() + .setFilter(Filter.eq(Filter.litteral("test4"), Filter.constant(12345678903L))))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-2", result.getEntities().get(0).getRowKey()); + } + + { + // Act + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() + .setFilter(Filter.eq(Filter.litteral("test5"), Filter.constant(new Date(3000)))))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } + + { + // Act + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() + .setFilter(Filter.eq(Filter.litteral("test6"), + Filter.constant(entities[3].getPropertyValue("test6")))))); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntities().size()); + assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey()); + } } @Test From adcf3ad86c73fa8f28595c240131e19a0295f7f3 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 12:40:13 -0700 Subject: [PATCH 44/76] Added new ISO8601 unit tests Added back "shortFormat" function, needed by Blob --- .../ContainerACLDateAdapter.java | 2 +- .../implementation/ISO8601DateConverter.java | 12 +++- .../ISO8601DateConverterTests.java | 61 ++++++++++++++++++- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java index af860e50936dd..f1ea12dbaa76f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java @@ -30,6 +30,6 @@ public Date unmarshal(String arg0) throws Exception { @Override public String marshal(Date arg0) throws Exception { - return new ISO8601DateConverter().format(arg0); + return new ISO8601DateConverter().shortFormat(arg0); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 9fa415c44c8f1..41bb096c7daa0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -28,8 +28,8 @@ public class ISO8601DateConverter { // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String DATETIME_PATTERN_NO_S = "yyyy-MM-dd'T'HH:mm'Z'"; - private static final String DATETIME_PATTERN_NO_MS = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final String DATETIME_PATTERN_TO_DECIMAL = "yyyy-MM-dd'T'HH:mm:ss."; public String format(Date date) { @@ -38,6 +38,12 @@ public String format(Date date) { return iso8601Format.format(date); } + public String shortFormat(Date date) { + DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US); + iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); + return iso8601Format.format(date); + } + public Date parse(String date) throws ParseException { if (date == null) return null; @@ -49,7 +55,7 @@ public Date parse(String date) throws ParseException { } else if (length == 20) { // [2012-01-04T23:21:59Z] length = 20 - return parseDateFromString(date, DATETIME_PATTERN_NO_MS); + return parseDateFromString(date, SHORT_DATETIME_PATTERN); } else if (length >= 22 && length <= 28) { // [2012-01-04T23:21:59.1Z] length = 22 @@ -72,7 +78,7 @@ else if (length >= 22 && length <= 28) { } } - public static Date parseDateFromString(final String value, final String pattern) throws ParseException { + private static Date parseDateFromString(final String value, final String pattern) throws ParseException { DateFormat iso8601Format = new SimpleDateFormat(pattern, Locale.US); iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT")); return iso8601Format.parse(value); diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java index a777b001c5841..c69d18d8f1ebb 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java @@ -16,7 +16,9 @@ import static org.junit.Assert.*; +import java.util.Calendar; import java.util.Date; +import java.util.TimeZone; import org.junit.Test; @@ -29,9 +31,23 @@ public void shortFormatWorks() throws Exception { // Act Date result = converter.parse(value); + String value2 = converter.format(result); // Assert assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 0, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.000Z", value2); } @Test @@ -42,9 +58,50 @@ public void longFormatWorks() throws Exception { // Act Date result = converter.parse(value); + String value2 = converter.format(result); // Assert assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 123, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.123Z", value2); + } + + @Test + public void mixedFormatWorks() throws Exception { + // Arrange + ISO8601DateConverter converter = new ISO8601DateConverter(); + String value = "2012-01-12T00:35:58.12Z"; + + // Act + Date result = converter.parse(value); + String value2 = converter.format(result); + + // Assert + assertNotNull(result); + + Calendar calendar = Calendar.getInstance(); + calendar.setTime(result); + calendar.setTimeZone(TimeZone.getTimeZone("GMT")); + assertEquals("Year", 2012, calendar.get(Calendar.YEAR)); + assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1); + assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH)); + assertEquals("Hour", 0, calendar.get(Calendar.HOUR)); + assertEquals("Minute", 35, calendar.get(Calendar.MINUTE)); + assertEquals("Second", 58, calendar.get(Calendar.SECOND)); + assertEquals("Millisecond", 120, calendar.get(Calendar.MILLISECOND)); + + assertEquals("2012-01-12T00:35:58.120Z", value2); } @Test @@ -55,10 +112,12 @@ public void shortFormatRoundTrips() throws Exception { // Act Date result = converter.parse(value); - String value2 = converter.format(result); + String value2 = converter.shortFormat(result); + String value3 = converter.format(result); // Assert assertNotNull(result); assertEquals(value, value2); + assertEquals("2012-01-12T00:35:58.000Z", value3); } } From 2962f94c79ebbcc8b5db68c26daa18b6c75b27c9 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 12:50:05 -0700 Subject: [PATCH 45/76] Making variable name more descriptive. --- .../services/blob/implementation/ISO8601DateConverter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java index 41bb096c7daa0..34444b858a039 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -69,9 +69,9 @@ else if (length >= 22 && length <= 28) { Float secondDecimal = Float.parseFloat(secondDecimalString); int milliseconds = Math.round(secondDecimal * 1000); long timeInMS = timeWithSecondGranularity + milliseconds; - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(timeInMS); - return cal.getTime(); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(timeInMS); + return calendar.getTime(); } else { throw new IllegalArgumentException(String.format("Invalid Date String: %s", date)); From 2116310239a320d9083fc33256948479209212ce Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 14:14:02 -0700 Subject: [PATCH 46/76] Added unit test Changed catch statement to perform a reasonable fallback if the URLEncoder call fails. Added missing imports statements. --- .../table/implementation/TableRestProxy.java | 15 ++++++----- .../table/TableServiceIntegrationTest.java | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index e9a5dbc846493..10c343b87bb94 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -18,7 +18,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.net.URI; +import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; @@ -159,16 +161,17 @@ private List<String> encodeODataURIValues(List<String> values) { } private String getEntityPath(String table, String partitionKey, String rowKey) { - String ret = "error"; + return table + "(" + "PartitionKey='" + safeEncode(partitionKey) + "',RowKey='" + safeEncode(rowKey) + "')"; + } + + private String safeEncode(String input) { + String fixSingleQuotes = input.replace("'", "''"); try { - ret = table + "(" + "PartitionKey='" - + URLEncoder.encode(partitionKey, "UTF-8").replace("+", "%20").replace("'", "%27") + "',RowKey='" - + URLEncoder.encode(rowKey, "UTF-8").replace("+", "%20").replace("'", "%27") + "')"; - System.out.println("ret : " + ret); + return URLEncoder.encode(fixSingleQuotes, "UTF-8").replace("+", "%20"); } catch (UnsupportedEncodingException e) { + return fixSingleQuotes; } - return ret; } private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 4a4146a5055db..7a7de013076c9 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -437,6 +437,32 @@ public void deleteEntityWorks() throws Exception { // Assert } + @Test + public void deleteEntityTroublesomeKeyWorks() throws Exception { + System.out.println("deleteEntityTroublesomeKeyWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + Entity entity1 = new Entity().setPartitionKey("001").setRowKey("key with spaces"); + Entity entity2 = new Entity().setPartitionKey("001").setRowKey("key'with'quotes"); + Entity entity3 = new Entity().setPartitionKey("001").setRowKey("keyWithUnicode \uB2E4"); + Entity entity4 = new Entity().setPartitionKey("001").setRowKey("key 'with'' \uB2E4"); + + // Act + InsertEntityResult result1 = service.insertEntity(TEST_TABLE_2, entity1); + InsertEntityResult result2 = service.insertEntity(TEST_TABLE_2, entity2); + InsertEntityResult result3 = service.insertEntity(TEST_TABLE_2, entity3); + InsertEntityResult result4 = service.insertEntity(TEST_TABLE_2, entity4); + + service.deleteEntity(TEST_TABLE_2, result1.getEntity().getPartitionKey(), result1.getEntity().getRowKey()); + service.deleteEntity(TEST_TABLE_2, result2.getEntity().getPartitionKey(), result2.getEntity().getRowKey()); + service.deleteEntity(TEST_TABLE_2, result3.getEntity().getPartitionKey(), result3.getEntity().getRowKey()); + service.deleteEntity(TEST_TABLE_2, result4.getEntity().getPartitionKey(), result4.getEntity().getRowKey()); + + // Assert + } + @Test public void deleteEntityWithETagWorks() throws Exception { System.out.println("deleteEntityWithETagWorks()"); From 90442d27eb5ddcc8d38ec61acf6eb4cca4b78a2d Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Tue, 3 Apr 2012 21:36:53 -0700 Subject: [PATCH 47/76] Address root cause of the issue: the default response EntityInputStream not rewindable, yet is is needed in multiple areas of the parseBatchResponse. Revert workaround fix. --- .../services/core/utils/pipeline/Exports.java | 3 +-- .../table/implementation/TableRestProxy.java | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java index f7aae2cd9188f..234581b023fb8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java @@ -22,7 +22,6 @@ import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; -import com.sun.jersey.api.client.filter.LoggingFilter; public class Exports implements Builder.Exports { @@ -53,7 +52,7 @@ public Client create(String profile, Builder builder, Map<String, Object> proper public HttpURLConnectionClient create(String profile, Builder builder, Map<String, Object> properties) { ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig"); HttpURLConnectionClient client = HttpURLConnectionClient.create(clientConfig); - client.addFilter(new LoggingFilter()); + //client.addFilter(new LoggingFilter()); return client; } }); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index bfeddfdb5c807..54b58bd8f20a6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -83,6 +83,7 @@ import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; import com.sun.jersey.core.header.InBoundHeaders; +import com.sun.jersey.core.util.ReaderWriter; public class TableRestProxy implements TableContract { private static final String API_VERSION = "2011-08-18"; @@ -577,7 +578,13 @@ public BatchResult batch(BatchOperations operations, TableServiceOptions options ThrowIfError(response); BatchResult result = new BatchResult(); - result.setEntries(parseBatchResponse(response, operations)); + + try { + result.setEntries(parseBatchResponse(response, operations)); + } + catch (IOException e) { + throw new ServiceException(e); + } return result; } @@ -696,7 +703,15 @@ private DataSource createBatchDeleteEntityPart(String table, String partitionKey return bodyPartContent; } - private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations operations) { + private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations operations) throws IOException { + // Default stream cannot be reset, but it is needed by multiple parts of this method. + // Replace the default response stream with one that can be read multiple times. + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStream in = response.getEntityInputStream(); + ReaderWriter.writeTo(in, out); + byte[] requestEntity = out.toByteArray(); + response.setEntityInputStream(new ByteArrayInputStream(requestEntity)); + List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); From bea4d6ce2e7a87b429b56623d0518ff0028c328b Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 4 Apr 2012 09:22:55 -0700 Subject: [PATCH 48/76] Adding more validation to unit test --- .../table/TableServiceIntegrationTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 7a7de013076c9..aca02d15bc3b8 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -461,6 +461,27 @@ public void deleteEntityTroublesomeKeyWorks() throws Exception { service.deleteEntity(TEST_TABLE_2, result4.getEntity().getPartitionKey(), result4.getEntity().getRowKey()); // Assert + try { + service.getEntity(TEST_TABLE_2, result1.getEntity().getPartitionKey(), result1.getEntity().getRowKey()); + assertFalse("Expect an exception when getting an entity that does not exist", true); + } + catch (ServiceException e) { + assertEquals("expect getHttpStatusCode", 404, e.getHttpStatusCode()); + + } + + QueryEntitiesResult assertResult2 = service.queryEntities( + TEST_TABLE_2, + new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), + Filter.constant("key'with'quotes"))))); + + assertEquals(0, assertResult2.getEntities().size()); + + QueryEntitiesResult assertResult3 = service.queryEntities(TEST_TABLE_2); + for (Entity entity : assertResult3.getEntities()) { + assertFalse("Entity3 should be removed from the table", entity3.getRowKey().equals(entity.getRowKey())); + assertFalse("Entity4 should be removed from the table", entity4.getRowKey().equals(entity.getRowKey())); + } } @Test From bbf8b07c835d45eaebbd1738def9d35d640044b5 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 4 Apr 2012 10:14:13 -0700 Subject: [PATCH 49/76] Minor name changes. --- .../services/table/implementation/TableRestProxy.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 54b58bd8f20a6..0e44678565088 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -706,11 +706,10 @@ private DataSource createBatchDeleteEntityPart(String table, String partitionKey private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations operations) throws IOException { // Default stream cannot be reset, but it is needed by multiple parts of this method. // Replace the default response stream with one that can be read multiple times. - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream in = response.getEntityInputStream(); - ReaderWriter.writeTo(in, out); - byte[] requestEntity = out.toByteArray(); - response.setEntityInputStream(new ByteArrayInputStream(requestEntity)); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + InputStream inputStream = response.getEntityInputStream(); + ReaderWriter.writeTo(inputStream, byteArrayOutputStream); + response.setEntityInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())); List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); From dc66cdafe78750f63d73958f621f0aadc36c6f63 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 4 Apr 2012 14:46:35 -0700 Subject: [PATCH 50/76] Add import statement missing from merge --- .../windowsazure/services/table/TableServiceIntegrationTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index daf68d549f11b..20ffb6596727f 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.HashSet; import java.util.Set; +import java.util.UUID; import org.junit.AfterClass; import org.junit.BeforeClass; From 9a503baca68957372845668b108b9be76887357a Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 4 Apr 2012 23:06:20 -0700 Subject: [PATCH 51/76] Simplify the processing of the batch error messages to avoid Jersey SEVERE warnings. Throw a reasonable error when the Housekeeping: Add suppression markers for unfixable warnings. --- .../implementation/HttpReaderWriter.java | 1 + .../table/implementation/TableRestProxy.java | 46 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java index a0120eb419846..347538853151a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java @@ -92,6 +92,7 @@ public void appendMethod(OutputStream stream, String verb, URI uri) { public void appendHeaders(OutputStream stream, InternetHeaders headers) { try { // Headers + @SuppressWarnings("unchecked") Enumeration<Header> e = headers.getAllHeaders(); while (e.hasMoreElements()) { Header header = e.nextElement(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 013c109da8edd..d0a4e23f6b6d8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.Date; import java.util.Enumeration; -import java.util.HashSet; import java.util.Formatter; import java.util.List; import java.util.UUID; @@ -92,10 +91,6 @@ import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource.Builder; import com.sun.jersey.core.header.InBoundHeaders; -import com.sun.jersey.core.spi.component.ProviderFactory; -import com.sun.jersey.core.spi.component.ProviderServices; -import com.sun.jersey.core.spi.factory.MessageBodyFactory; -import com.sun.jersey.spi.inject.ClientSide; import com.sun.jersey.core.util.ReaderWriter; public class TableRestProxy implements TableContract { @@ -808,6 +803,13 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations List<DataSource> parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() .getFirst("Content-Type")); + if (parts.size() == 0 || parts.size() > operations.getOperations().size()) { + throw new UniformInterfaceException(String.format( + "Batch response from server does not contain the correct amount " + + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations() + .size()), response); + } + Entry[] entries = new Entry[operations.getOperations().size()]; for (int i = 0; i < parts.size(); i++) { DataSource ds = parts.get(i); @@ -816,24 +818,22 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations StatusLine status = httpReaderWriter.parseStatusLine(ds); InternetHeaders headers = httpReaderWriter.parseHeaders(ds); InputStream content = httpReaderWriter.parseEntity(ds); + ByteArrayOutputStream contentByteArrayOutputStream = new ByteArrayOutputStream(); + ReaderWriter.writeTo(content, contentByteArrayOutputStream); + content = new ByteArrayInputStream(contentByteArrayOutputStream.toByteArray()); if (status.getStatus() >= 400) { // Create dummy client response with status, headers and content InBoundHeaders inBoundHeaders = new InBoundHeaders(); + @SuppressWarnings("unchecked") Enumeration<Header> e = headers.getAllHeaders(); while (e.hasMoreElements()) { Header header = e.nextElement(); inBoundHeaders.putSingle(header.getName(), header.getValue()); } - ProviderServices providerServices = new ProviderServices(ClientSide.class, new ProviderFactory(null), - new HashSet<Class<?>>(), new HashSet<Class<?>>()); - MessageBodyFactory bodyContext = new MessageBodyFactory(providerServices, false); - // TODO: This call causes lots of warnings from Jersey. Need to figure out how to silence them. - bodyContext.init(); - ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, - bodyContext); + ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, null); // Wrap into a ServiceException UniformInterfaceException exception = new UniformInterfaceException(dummyResponse); @@ -844,26 +844,28 @@ private List<Entry> parseBatchResponse(ClientResponse response, BatchOperations // Parse the message to find which operation caused this error. try { XMLInputFactory xmlStreamFactory = XMLInputFactory.newFactory(); - XMLStreamReader xmlr = xmlStreamFactory.createXMLStreamReader(new ByteArrayInputStream( - serviceException.getRawResponseBody().getBytes())); - while (xmlr.hasNext()) { - xmlr.next(); - if (xmlr.isStartElement() && "message".equals(xmlr.getLocalName())) { - xmlr.next(); + content.reset(); + XMLStreamReader xmlStreamReader = xmlStreamFactory.createXMLStreamReader(content); + + while (xmlStreamReader.hasNext()) { + xmlStreamReader.next(); + if (xmlStreamReader.isStartElement() && "message".equals(xmlStreamReader.getLocalName())) { + xmlStreamReader.next(); // Process "message" elements only - String message = xmlr.getText(); + String message = xmlStreamReader.getText(); int colonIndex = message.indexOf(':'); String errorOpId = message.substring(0, colonIndex); int opId = Integer.parseInt(errorOpId); entries[opId] = error; + break; } } - xmlr.close(); + xmlStreamReader.close(); } catch (XMLStreamException e1) { - // TODO: What to throw here? + throw new UniformInterfaceException( + "Batch response from server does not contain XML in the expected format", response); } - } else if (operation instanceof InsertEntityOperation) { InsertEntity opResult = new InsertEntity().setEntity(atomReaderWriter.parseEntityEntry(content)); From 92b1db53d7b62f2856491dc0859629dfff7e39ad Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 4 Apr 2012 23:32:22 -0700 Subject: [PATCH 52/76] Adding unit test for batches that have errors. --- .../table/TableServiceIntegrationTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 20ffb6596727f..88a4e0c1c130c 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -1023,4 +1023,52 @@ public void batchAllOperationsTogetherWorks() throws Exception { assertEquals(UpdateEntity.class, result.getEntries().get(4).getClass()); assertEquals(UpdateEntity.class, result.getEntries().get(5).getClass()); } + + @Test + public void batchNegativeWorks() throws Exception { + System.out.println("batchNegativeWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + String table = TEST_TABLE_8; + String partitionKey = "001"; + + // Insert an entity the modify it outside of the batch + Entity entity1 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks1") + .setProperty("test", EdmType.INT32, 1); + Entity entity2 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks2") + .setProperty("test", EdmType.INT32, 2); + Entity entity3 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks3") + .setProperty("test", EdmType.INT32, 3); + + entity1 = service.insertEntity(table, entity1).getEntity(); + entity2 = service.insertEntity(table, entity2).getEntity(); + entity2.setProperty("test", EdmType.INT32, -2); + service.updateEntity(table, entity2); + + // Act + BatchOperations batchOperations = new BatchOperations(); + + // The entity1 still has the original etag from the first submit, + // so this update should fail, because another update was already made. + entity1.setProperty("test", EdmType.INT32, 3); + batchOperations.addDeleteEntity(table, entity1.getPartitionKey(), entity1.getRowKey(), entity1.getEtag()); + batchOperations.addUpdateEntity(table, entity2); + batchOperations.addInsertEntity(table, entity3); + + BatchResult result = service.batch(batchOperations); + + // Assert + assertNotNull(result); + assertEquals(batchOperations.getOperations().size(), result.getEntries().size()); + assertNull("First result should be null", result.getEntries().get(0)); + assertNotNull("Second result should not be null", result.getEntries().get(1)); + assertEquals("Second result type", com.microsoft.windowsazure.services.table.models.BatchResult.Error.class, + result.getEntries().get(1).getClass()); + com.microsoft.windowsazure.services.table.models.BatchResult.Error error = (com.microsoft.windowsazure.services.table.models.BatchResult.Error) result + .getEntries().get(1); + assertEquals("Second result status code", 412, error.getError().getHttpStatusCode()); + assertNull("Third result should be null", result.getEntries().get(2)); + } } From d14c5c9c5a690e382747cd11b7cef81a325e9c59 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 5 Apr 2012 12:17:09 -0700 Subject: [PATCH 53/76] Fix minor spacing issues --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6390ff1f65f7b..76a899e45b209 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #Windows Azure SDK for Java -This SDK allows you to build Windows Azure applications in Java that allow +This SDK allows you to build Windows Azure applications in Java that allow you to take advantage of Azure scalable cloud computing resources: table and blob storage, messaging through Service Bus. @@ -68,9 +68,9 @@ and uploading a file to it. For additional information on using the client libr public class BlobSample { public static final String storageConnectionString = - "DefaultEndpointsProtocol=http;" + - "AccountName=your_account_name;" + - "AccountKey= your_account_name"; + "DefaultEndpointsProtocol=http;" + + "AccountName=your_account_name;" + + "AccountKey= your_account_name"; public static void main(String[] args) { From 564d8aa92e6835c633dd26d3c459ebbc23f87060 Mon Sep 17 00:00:00 2001 From: unknown <gongchen@HEALTHYGCHENG04.redmond.corp.microsoft.com> Date: Thu, 5 Apr 2012 15:17:55 -0700 Subject: [PATCH 54/76] fix the failed java unit test related to getBlob. --- .../blob/implementation/BlobRestProxy.java | 22 +++++++++++-------- .../core/utils/pipeline/PipelineHelpers.java | 14 +++++++++--- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java index 5bba95ef4599c..861cd21f3672f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.implementation; @@ -122,6 +122,10 @@ private void ThrowIfError(ClientResponse r) { PipelineHelpers.ThrowIfError(r); } + private void ThrowIfNotSuccess(ClientResponse clientResponse) { + PipelineHelpers.ThrowIfNotSuccess(clientResponse); + } + private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) { return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } @@ -630,7 +634,7 @@ public GetBlobResult getBlob(String container, String blob, GetBlobOptions optio builder = addOptionalAccessContitionHeader(builder, options.getAccessCondition()); ClientResponse response = builder.get(ClientResponse.class); - ThrowIfError(response); + ThrowIfNotSuccess(response); GetBlobPropertiesResult properties = getBlobPropertiesResultFromResponse(response); GetBlobResult blobResult = new GetBlobResult(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java index a36de8dc02476..21b95538f6e11 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java @@ -27,9 +27,17 @@ import com.sun.jersey.api.client.WebResource.Builder; public class PipelineHelpers { - public static void ThrowIfError(ClientResponse r) { - if (r.getStatus() >= 400) { - throw new UniformInterfaceException(r); + public static void ThrowIfNotSuccess(ClientResponse clientResponse) { + int statusCode = clientResponse.getStatus(); + + if ((statusCode < 200) || (statusCode >= 300)) { + throw new UniformInterfaceException(clientResponse); + } + } + + public static void ThrowIfError(ClientResponse clientResponse) { + if (clientResponse.getStatus() >= 400) { + throw new UniformInterfaceException(clientResponse); } } From c884a331b186c3e2848c72f2f4b770ebae292e69 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 5 Apr 2012 15:30:42 -0700 Subject: [PATCH 55/76] Fix 236: Table: EdmType.GUID should map to java.util.UUID --- .../table/implementation/DefaultEdmValueConterter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java index 6988596979495..d35293670c1b8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java @@ -16,6 +16,7 @@ import java.text.ParseException; import java.util.Date; +import java.util.UUID; import javax.inject.Inject; @@ -80,6 +81,9 @@ else if (EdmType.INT64.equals(edmType)) { else if (EdmType.BINARY.equals(edmType)) { return Base64.decode(value); } + else if (EdmType.GUID.equals(edmType)) { + return UUID.fromString(value); + } return value; } From 2c82f1eea61e915f5dd104a8b2b06ee8c247a41b Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Thu, 5 Apr 2012 15:52:36 -0700 Subject: [PATCH 56/76] Added unit test for round-tripping UUID values --- .../services/table/TableServiceIntegrationTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 88a4e0c1c130c..55fc4c14fb3a0 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -302,10 +302,12 @@ public void insertEntityWorks() throws Exception { Configuration config = createConfiguration(); TableContract service = TableService.create(config); byte[] binaryData = new byte[] { 1, 2, 3, 4 }; + UUID uuid = UUID.randomUUID(); Entity entity = new Entity().setPartitionKey("001").setRowKey("insertEntityWorks") .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value") .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L) - .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData); + .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData) + .setProperty("test7", EdmType.GUID, uuid); // Act InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity); @@ -341,6 +343,10 @@ public void insertEntityWorks() throws Exception { for (int i = 0; i < binaryData.length; i++) { assertEquals(binaryData[i], returnedBinaryData[i]); } + + assertNotNull(result.getEntity().getProperty("test7")); + assertTrue(result.getEntity().getProperty("test7").getValue() instanceof UUID); + assertEquals(uuid.toString(), result.getEntity().getProperty("test7").getValue().toString()); } @Test From f338598b11d7fadde72deaa2b5319efbda00ed95 Mon Sep 17 00:00:00 2001 From: Albert Cheng <gongchen@microsoft.com> Date: Thu, 5 Apr 2012 17:34:13 -0700 Subject: [PATCH 57/76] Fix issue #217 Table: QueryTablesOptions.Query not fully used. --- .../table/implementation/TableRestProxy.java | 20 ++----------------- .../table/models/QueryTablesOptions.java | 10 ---------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index 604f51149d596..ce6c5e34a1fce 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -380,8 +380,7 @@ public QueryTablesResult queryTables() throws ServiceException { @Override public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { - - Query query = options.getQuery(); + Query query = new Query(); String nextTableName = options.getNextTableName(); String prefix = options.getPrefix(); @@ -389,22 +388,7 @@ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceE // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound is prefix + '{' Filter prefixFilter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(prefix)), Filter.le(Filter.litteral("TableName"), Filter.constant(prefix + "{"))); - - // a new query is needed if prefix alone is passed in - if (query == null) { - query = new Query(); - } - - // examine the existing filter on the query - if (query.getFilter() == null) { - // use the prefix filter if the query filter is null - query.setFilter(prefixFilter); - } - else { - // combine and use the prefix filter if the query filter exists - Filter combinedFilter = Filter.and(query.getFilter(), prefixFilter); - query.setFilter(combinedFilter); - } + query.setFilter(prefixFilter); } WebResource webResource = getResource(options).path("Tables"); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index 36b975d7a6900..f115f9ed52d8f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -16,18 +16,8 @@ public class QueryTablesOptions extends TableServiceOptions { private String nextTableName; - private Query query; private String prefix; - public Query getQuery() { - return query; - } - - public QueryTablesOptions setQuery(Query query) { - this.query = query; - return this; - } - public String getNextTableName() { return nextTableName; } From 8dd4620beea6df082b17dc4fd7cb4c8bff409cf0 Mon Sep 17 00:00:00 2001 From: Albert Cheng <gongchen@microsoft.com> Date: Fri, 6 Apr 2012 18:11:44 -0700 Subject: [PATCH 58/76] Making filter immutable, add filter to TableQuery, Rename RawStringFilter to QueryStringFilter, rename LiteralFilter to PropertyNameFilter. --- .../table/implementation/TableRestProxy.java | 54 +++++++------ .../services/table/models/BinaryFilter.java | 26 +++--- .../services/table/models/ConstantFilter.java | 11 ++- .../services/table/models/Filter.java | 28 +++---- ...ingFilter.java => PropertyNameFilter.java} | 14 ++-- .../services/table/models/Query.java | 81 ------------------- .../table/models/QueryEntitiesOptions.java | 75 ++++++++++++++--- ...eralFilter.java => QueryStringFilter.java} | 13 ++- .../table/models/QueryTablesOptions.java | 10 +++ .../services/table/models/UnaryFilter.java | 18 ++--- .../table/TableServiceIntegrationTest.java | 50 +++++++----- 11 files changed, 183 insertions(+), 197 deletions(-) rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/{RawStringFilter.java => PropertyNameFilter.java} (72%) delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/{LitteralFilter.java => QueryStringFilter.java} (73%) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java index ce6c5e34a1fce..7d74bbecba2aa 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -72,13 +72,12 @@ import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.LitteralFilter; -import com.microsoft.windowsazure.services.table.models.Query; +import com.microsoft.windowsazure.services.table.models.PropertyNameFilter; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; +import com.microsoft.windowsazure.services.table.models.QueryStringFilter; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; import com.microsoft.windowsazure.services.table.models.QueryTablesResult; -import com.microsoft.windowsazure.services.table.models.RawStringFilter; import com.microsoft.windowsazure.services.table.models.ServiceProperties; import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.microsoft.windowsazure.services.table.models.UnaryFilter; @@ -182,26 +181,29 @@ private WebResource addOptionalQueryParam(WebResource webResource, String key, O return PipelineHelpers.addOptionalQueryParam(webResource, key, value); } - private WebResource addOptionalQuery(WebResource webResource, Query query) { - if (query == null) + private WebResource addOptionalQueryEntitiesOptions(WebResource webResource, + QueryEntitiesOptions queryEntitiesOptions) { + if (queryEntitiesOptions == null) return webResource; - if (query.getSelectFields() != null && query.getSelectFields().size() > 0) { + if (queryEntitiesOptions.getSelectFields() != null && queryEntitiesOptions.getSelectFields().size() > 0) { webResource = addOptionalQueryParam(webResource, "$select", - CommaStringBuilder.join(encodeODataURIValues(query.getSelectFields()))); + CommaStringBuilder.join(encodeODataURIValues(queryEntitiesOptions.getSelectFields()))); } - if (query.getTop() != null) { - webResource = addOptionalQueryParam(webResource, "$top", encodeODataURIValue(query.getTop().toString())); + if (queryEntitiesOptions.getTop() != null) { + webResource = addOptionalQueryParam(webResource, "$top", encodeODataURIValue(queryEntitiesOptions.getTop() + .toString())); } - if (query.getFilter() != null) { - webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(query.getFilter())); + if (queryEntitiesOptions.getFilter() != null) { + webResource = addOptionalQueryParam(webResource, "$filter", + buildFilterExpression(queryEntitiesOptions.getFilter())); } - if (query.getOrderByFields() != null) { + if (queryEntitiesOptions.getOrderByFields() != null) { webResource = addOptionalQueryParam(webResource, "$orderby", - CommaStringBuilder.join(encodeODataURIValues(query.getOrderByFields()))); + CommaStringBuilder.join(encodeODataURIValues(queryEntitiesOptions.getOrderByFields()))); } return webResource; @@ -217,8 +219,8 @@ private void buildFilterExpression(Filter filter, StringBuilder sb) { if (filter == null) return; - if (filter instanceof LitteralFilter) { - sb.append(((LitteralFilter) filter).getLitteral()); + if (filter instanceof PropertyNameFilter) { + sb.append(((PropertyNameFilter) filter).getPropertyName()); } else if (filter instanceof ConstantFilter) { Object value = ((ConstantFilter) filter).getValue(); @@ -282,8 +284,8 @@ else if (filter instanceof BinaryFilter) { buildFilterExpression(((BinaryFilter) filter).getRight(), sb); sb.append(")"); } - else if (filter instanceof RawStringFilter) { - sb.append(((RawStringFilter) filter).getRawString()); + else if (filter instanceof QueryStringFilter) { + sb.append(((QueryStringFilter) filter).getQueryString()); } } @@ -380,19 +382,25 @@ public QueryTablesResult queryTables() throws ServiceException { @Override public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException { - Query query = new Query(); + Filter queryFilter = options.getFilter(); String nextTableName = options.getNextTableName(); String prefix = options.getPrefix(); if (prefix != null) { // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound is prefix + '{' - Filter prefixFilter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(prefix)), - Filter.le(Filter.litteral("TableName"), Filter.constant(prefix + "{"))); - query.setFilter(prefixFilter); + Filter prefixFilter = Filter.and(Filter.ge(Filter.propertyName("TableName"), Filter.constant(prefix)), + Filter.le(Filter.propertyName("TableName"), Filter.constant(prefix + "{"))); + + if (queryFilter == null) { + queryFilter = prefixFilter; + } + else { + queryFilter = Filter.and(queryFilter, prefixFilter); + } } WebResource webResource = getResource(options).path("Tables"); - webResource = addOptionalQuery(webResource, query); + webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(queryFilter)); webResource = addOptionalQueryParam(webResource, "NextTableName", nextTableName); WebResource.Builder builder = webResource.getRequestBuilder(); @@ -609,7 +617,7 @@ public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions opti options = new QueryEntitiesOptions(); WebResource webResource = getResource(options).path(table); - webResource = addOptionalQuery(webResource, options.getQuery()); + webResource = addOptionalQueryEntitiesOptions(webResource, options); webResource = addOptionalQueryParam(webResource, "NextPartitionKey", encodeODataURIValue(options.getNextPartitionKey())); webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(options.getNextRowKey())); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java index 2cdb8473a26d3..7666da869e056 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java @@ -15,34 +15,26 @@ package com.microsoft.windowsazure.services.table.models; public class BinaryFilter extends Filter { - private String operator; - private Filter left; - private Filter right; + private final String operator; + private final Filter left; + private final Filter right; - public String getOperator() { - return operator; + public BinaryFilter(Filter left, String operator, Filter right) { + this.left = left; + this.operator = operator; + this.right = right; } - public BinaryFilter setOperator(String operator) { - this.operator = operator; - return this; + public String getOperator() { + return operator; } public Filter getLeft() { return left; } - public BinaryFilter setLeft(Filter left) { - this.left = left; - return this; - } - public Filter getRight() { return right; } - public BinaryFilter setRight(Filter right) { - this.right = right; - return this; - } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java index 098b28ae0e397..aa3dd041be7cc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -15,14 +15,13 @@ package com.microsoft.windowsazure.services.table.models; public class ConstantFilter extends Filter { - private Object value; + private final Object value; - public Object getValue() { - return value; + public ConstantFilter(Object value) { + this.value = value; } - public ConstantFilter setValue(Object value) { - this.value = value; - return this; + public Object getValue() { + return value; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index 31a6daf581b22..86dfafb309f6e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -16,50 +16,50 @@ public class Filter { public static UnaryFilter not(Filter operand) { - return new UnaryFilter().setOperator("not").setOperand(operand); + return new UnaryFilter("not", operand); } public static BinaryFilter and(Filter left, Filter right) { - return new BinaryFilter().setOperator("and").setLeft(left).setRight(right); + return new BinaryFilter(left, "and", right); } public static BinaryFilter or(Filter left, Filter right) { - return new BinaryFilter().setOperator("or").setLeft(left).setRight(right); + return new BinaryFilter(left, "or", right); } public static BinaryFilter eq(Filter left, Filter right) { - return new BinaryFilter().setOperator("eq").setLeft(left).setRight(right); + return new BinaryFilter(left, "eq", right); } public static BinaryFilter ne(Filter left, Filter right) { - return new BinaryFilter().setOperator("ne").setLeft(left).setRight(right); + return new BinaryFilter(left, "ne", right); } public static BinaryFilter ge(Filter left, Filter right) { - return new BinaryFilter().setOperator("ge").setLeft(left).setRight(right); + return new BinaryFilter(left, "ge", right); } public static BinaryFilter gt(Filter left, Filter right) { - return new BinaryFilter().setOperator("gt").setLeft(left).setRight(right); + return new BinaryFilter(left, "gt", right); } public static BinaryFilter lt(Filter left, Filter right) { - return new BinaryFilter().setOperator("lt").setLeft(left).setRight(right); + return new BinaryFilter(left, "lt", right); } public static BinaryFilter le(Filter left, Filter right) { - return new BinaryFilter().setOperator("le").setLeft(left).setRight(right); + return new BinaryFilter(left, "le", right); } public static ConstantFilter constant(Object value) { - return new ConstantFilter().setValue(value); + return new ConstantFilter(value); } - public static LitteralFilter litteral(String value) { - return new LitteralFilter().setLitteral(value); + public static PropertyNameFilter propertyName(String value) { + return new PropertyNameFilter(value); } - public static RawStringFilter rawString(String value) { - return new RawStringFilter().setRawString(value); + public static QueryStringFilter QueryString(String value) { + return new QueryStringFilter(value); } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java similarity index 72% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java index 12aaedf407d4e..49cb129d86e03 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java @@ -14,15 +14,15 @@ */ package com.microsoft.windowsazure.services.table.models; -public class RawStringFilter extends Filter { - private String rawString; +public class PropertyNameFilter extends Filter { + private final String propertyName; - public String getRawString() { - return rawString; + public PropertyNameFilter(String propertyName) { + this.propertyName = propertyName; } - public RawStringFilter setRawString(String rawString) { - this.rawString = rawString; - return this; + public String getPropertyName() { + return propertyName; } + } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java deleted file mode 100644 index cf3997dd7225c..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2012 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.models; - -import java.util.ArrayList; -import java.util.List; - -public class Query { - private List<String> selectFields = new ArrayList<String>(); - private String from; - private Filter filter; - private List<String> orderByFields = new ArrayList<String>(); - private Integer top; - - public List<String> getSelectFields() { - return selectFields; - } - - public Query setSelectFields(List<String> selectFields) { - this.selectFields = selectFields; - return this; - } - - public Query addSelectField(String selectField) { - this.selectFields.add(selectField); - return this; - } - - public String getFrom() { - return from; - } - - public Query setFrom(String from) { - this.from = from; - return this; - } - - public Filter getFilter() { - return filter; - } - - public Query setFilter(Filter filter) { - this.filter = filter; - return this; - } - - public List<String> getOrderByFields() { - return orderByFields; - } - - public Query setOrderByFields(List<String> orderByFields) { - this.orderByFields = orderByFields; - return this; - } - - public Query addOrderByField(String orderByField) { - this.orderByFields.add(orderByField); - return this; - } - - public Integer getTop() { - return top; - } - - public Query setTop(Integer top) { - this.top = top; - return this; - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index 31c7d05652bc7..e5d7a7c511513 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -14,19 +14,19 @@ */ package com.microsoft.windowsazure.services.table.models; +import java.util.ArrayList; +import java.util.List; + public class QueryEntitiesOptions extends TableServiceOptions { - private Query query; - public String nextPartitionKey; - public String nextRowKey; - public Query getQuery() { - return query; - } + private List<String> selectFields = new ArrayList<String>(); + private String from; + private Filter filter; + private List<String> orderByFields = new ArrayList<String>(); + private Integer top; - public QueryEntitiesOptions setQuery(Query query) { - this.query = query; - return this; - } + public String nextPartitionKey; + public String nextRowKey; public String getNextPartitionKey() { return nextPartitionKey; @@ -45,4 +45,59 @@ public QueryEntitiesOptions setNextRowKey(String nextRowKey) { this.nextRowKey = nextRowKey; return this; } + + public List<String> getSelectFields() { + return selectFields; + } + + public QueryEntitiesOptions setSelectFields(List<String> selectFields) { + this.selectFields = selectFields; + return this; + } + + public QueryEntitiesOptions addSelectField(String selectField) { + this.selectFields.add(selectField); + return this; + } + + public String getFrom() { + return from; + } + + public QueryEntitiesOptions setFrom(String from) { + this.from = from; + return this; + } + + public Filter getFilter() { + return filter; + } + + public QueryEntitiesOptions setFilter(Filter filter) { + this.filter = filter; + return this; + } + + public List<String> getOrderByFields() { + return orderByFields; + } + + public QueryEntitiesOptions setOrderByFields(List<String> orderByFields) { + this.orderByFields = orderByFields; + return this; + } + + public QueryEntitiesOptions addOrderByField(String orderByField) { + this.orderByFields.add(orderByField); + return this; + } + + public Integer getTop() { + return top; + } + + public QueryEntitiesOptions setTop(Integer top) { + this.top = top; + return this; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java similarity index 73% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java index 08a62dbf957b5..77341e2477c57 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java @@ -14,15 +14,14 @@ */ package com.microsoft.windowsazure.services.table.models; -public class LitteralFilter extends Filter { - private String litteral; +public class QueryStringFilter extends Filter { + private final String queryString; - public String getLitteral() { - return litteral; + public QueryStringFilter(String queryString) { + this.queryString = queryString; } - public LitteralFilter setLitteral(String litteral) { - this.litteral = litteral; - return this; + public String getQueryString() { + return queryString; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index f115f9ed52d8f..d72c580a402f4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -15,9 +15,19 @@ package com.microsoft.windowsazure.services.table.models; public class QueryTablesOptions extends TableServiceOptions { + private Filter filter; private String nextTableName; private String prefix; + public Filter getFilter() { + return filter; + } + + public QueryTablesOptions setFilter(Filter filter) { + this.filter = filter; + return this; + } + public String getNextTableName() { return nextTableName; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java index f6da13830a0cb..99aa90409830b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java @@ -15,24 +15,20 @@ package com.microsoft.windowsazure.services.table.models; public class UnaryFilter extends Filter { - private String operator; - private Filter operand; + private final String operator; + private final Filter operand; - public String getOperator() { - return operator; + public UnaryFilter(String operator, Filter operand) { + this.operator = operator; + this.operand = operand; } - public UnaryFilter setOperator(String operator) { - this.operator = operator; - return this; + public String getOperator() { + return operator; } public Filter getOperand() { return operand; } - public UnaryFilter setOperand(Filter operand) { - this.operand = operand; - return this; - } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index 20ffb6596727f..cb298162dc1d8 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -41,7 +41,6 @@ import com.microsoft.windowsazure.services.table.models.GetEntityResult; import com.microsoft.windowsazure.services.table.models.GetTableResult; import com.microsoft.windowsazure.services.table.models.InsertEntityResult; -import com.microsoft.windowsazure.services.table.models.Query; import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult; import com.microsoft.windowsazure.services.table.models.QueryTablesOptions; @@ -482,8 +481,8 @@ public void deleteEntityTroublesomeKeyWorks() throws Exception { QueryEntitiesResult assertResult2 = service.queryEntities( TEST_TABLE_2, - new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), - Filter.constant("key'with'quotes"))))); + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("RowKey"), + Filter.constant("key'with'quotes")))); assertEquals(0, assertResult2.getEntities().size()); @@ -672,9 +671,10 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, - new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"), - Filter.constant("queryEntitiesWithFilterWorks-3"))))); + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("RowKey"), + Filter.constant("queryEntitiesWithFilterWorks-3")))); // Assert assertNotNull(result); @@ -684,8 +684,8 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() - .setFilter(Filter.rawString("RowKey eq 'queryEntitiesWithFilterWorks-3'")))); + QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setFilter(Filter + .QueryString("RowKey eq 'queryEntitiesWithFilterWorks-3'"))); // Assert assertNotNull(result); @@ -695,10 +695,11 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities( - table, - new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("test"), - Filter.constant(true))))); + QueryEntitiesResult result = service + .queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test"), + Filter.constant(true)))); // Assert assertNotNull(result); @@ -707,8 +708,10 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() - .setFilter(Filter.eq(Filter.litteral("test2"), Filter.constant("'value'3"))))); + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test2"), + Filter.constant("'value'3")))); // Assert assertNotNull(result); @@ -718,8 +721,10 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() - .setFilter(Filter.eq(Filter.litteral("test4"), Filter.constant(12345678903L))))); + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test4"), + Filter.constant(12345678903L)))); // Assert assertNotNull(result); @@ -729,8 +734,10 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() - .setFilter(Filter.eq(Filter.litteral("test5"), Filter.constant(new Date(3000)))))); + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test5"), + Filter.constant(new Date(3000))))); // Assert assertNotNull(result); @@ -740,9 +747,10 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act - QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query() - .setFilter(Filter.eq(Filter.litteral("test6"), - Filter.constant(entities[3].getPropertyValue("test6")))))); + QueryEntitiesResult result = service.queryEntities( + table, + new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test6"), + Filter.constant(entities[3].getPropertyValue("test6"))))); // Assert assertNotNull(result); From 4d824146bab6291247881c042722e3fc3bb09eeb Mon Sep 17 00:00:00 2001 From: Albert Cheng <gongchen@microsoft.com> Date: Mon, 9 Apr 2012 12:15:39 -0700 Subject: [PATCH 59/76] Addressing code review feedback... --- .../microsoft/windowsazure/services/table/models/Filter.java | 2 +- .../services/table/TableServiceIntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index 86dfafb309f6e..1e410d2adfb46 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -59,7 +59,7 @@ public static PropertyNameFilter propertyName(String value) { return new PropertyNameFilter(value); } - public static QueryStringFilter QueryString(String value) { + public static QueryStringFilter queryString(String value) { return new QueryStringFilter(value); } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java index cb298162dc1d8..9301537ca18f0 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -685,7 +685,7 @@ public void queryEntitiesWithFilterWorks() throws Exception { { // Act QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setFilter(Filter - .QueryString("RowKey eq 'queryEntitiesWithFilterWorks-3'"))); + .queryString("RowKey eq 'queryEntitiesWithFilterWorks-3'"))); // Assert assertNotNull(result); From 40c5baa16e7e025b8ca608f5233c372a8972f6b4 Mon Sep 17 00:00:00 2001 From: "U-REDMOND\\gongchen" <gongchen@HEALTHYGCHENG04.redmond.corp.microsoft.com> Date: Mon, 9 Apr 2012 12:25:02 -0700 Subject: [PATCH 60/76] update the version number to 0.2.1 --- microsoft-azure-api/pom.xml | 456 ++++++++++++++++++------------------ 1 file changed, 228 insertions(+), 228 deletions(-) diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index c83d5dcc1a333..01567851ee74f 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -1,228 +1,228 @@ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>com.microsoft.windowsazure</groupId> - <artifactId>microsoft-windowsazure-api</artifactId> - <version>0.2.0</version> - <packaging>jar</packaging> - - <name>Microsoft Windows Azure Client API</name> - <description>API for Microsoft Windows Azure Clients</description> - <url>https://github.com/WindowsAzure/azure-sdk-for-java</url> - - <licenses> - <license> - <name>The Apache Software License, Version 2.0</name> - <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> - <distribution>repo</distribution> - </license> - </licenses> - - <scm> - <url>scm:git:https://github.com/WindowsAzure/azure-sdk-for-java</url> - <connection>scm:git:git://github.com/WindowsAzure/azure-sdk-for-java.git</connection> - </scm> - - <properties> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <legal><![CDATA[[INFO] Any downloads listed may be third party software. Microsoft grants you no rights for third party software.]]></legal> - </properties> - - <developers> - <developer> - <id>microsoft</id> - <name>Microsoft</name> - </developer> - </developers> - - <dependencies> - <dependency> - <groupId>com.sun.jersey</groupId> - <artifactId>jersey-client</artifactId> - <version>1.10-b02</version> - </dependency> - <dependency> - <groupId>javax.xml.bind</groupId> - <artifactId>jaxb-api</artifactId> - <version>2.1</version> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>4.8</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.hamcrest</groupId> - <artifactId>hamcrest-all</artifactId> - <version>1.1</version> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.mockito</groupId> - <artifactId>mockito-all</artifactId> - <scope>test</scope> - <version>1.9.0-rc1</version> - </dependency> - <dependency> - <groupId>javax.inject</groupId> - <artifactId>javax.inject</artifactId> - <version>1</version> - </dependency> - <dependency> - <groupId>com.sun.jersey</groupId> - <artifactId>jersey-json</artifactId> - <version>1.10-b02</version> - </dependency> - <dependency> - <groupId>commons-logging</groupId> - <artifactId>commons-logging</artifactId> - <version>1.1.1</version> - </dependency> - <dependency> - <groupId>javax.mail</groupId> - <artifactId>mail</artifactId> - <version>1.4</version> - </dependency> - <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> - <version>3.1</version> - </dependency> - </dependencies> - - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-help-plugin</artifactId> - <version>2.1.1</version> - <executions> - <execution> - <phase>validate</phase> - <goals> - <goal>evaluate</goal> - </goals> - <configuration> - <expression>legal</expression> - </configuration> - </execution> - </executions> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <version>2.3.2</version> - <configuration> - <source>1.6</source> - <target>1.6</target> - </configuration> - </plugin> - <plugin> - <groupId>org.jvnet.jaxb2.maven2</groupId> - <artifactId>maven-jaxb2-plugin</artifactId> - <version>0.8.0</version> - <executions> - <execution> - <phase>generate-sources</phase> - <goals> - <goal>generate</goal> - </goals> - </execution> - </executions> - <configuration> - <extension>true</extension> - <plugins> - <plugin> - <groupId>org.jvnet.jaxb2_commons</groupId> - <artifactId>jaxb2-basics</artifactId> - <version>0.6.0</version> - </plugin> - <plugin> - <groupId>org.jvnet.jaxb2_commons</groupId> - <artifactId>jaxb2-basics-annotate</artifactId> - <version>0.6.0</version> - </plugin> - </plugins> - - </configuration> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-javadoc-plugin</artifactId> - <version>2.8</version> - <configuration> - <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization</excludePackageNames> - <bottom><![CDATA[<code>/** -<br/>* Copyright 2011 Microsoft Corporation -<br/>* -<br/>* Licensed under the Apache License, Version 2.0 (the "License"); -<br/>* you may not use this file except in compliance with the License. -<br/>* You may obtain a copy of the License at -<br/>* http://www.apache.org/licenses/LICENSE-2.0 -<br/>* -<br/>* Unless required by applicable law or agreed to in writing, software -<br/>* distributed under the License is distributed on an "AS IS" BASIS, -<br/>* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -<br/>* See the License for the specific language governing permissions and -<br/>* limitations under the License. -<br/>*/</code>]]></bottom> - </configuration> - </plugin> - - <plugin> - <groupId>org.codehaus.mojo</groupId> - <artifactId>findbugs-maven-plugin</artifactId> - <version>2.3.2</version> - <configuration> - <xmlOutput>true</xmlOutput> - <findbugsXmlOutput>true</findbugsXmlOutput> - <findbugsXmlWithMessages>true</findbugsXmlWithMessages> - </configuration> - </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-checkstyle-plugin</artifactId> - <version>2.8</version> - <configuration> - <configLocation>src/config/checkstyle.xml</configLocation> - </configuration> - </plugin> - - - </plugins> - <pluginManagement> - <plugins> - <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.--> - <plugin> - <groupId>org.eclipse.m2e</groupId> - <artifactId>lifecycle-mapping</artifactId> - <version>1.0.0</version> - <configuration> - <lifecycleMappingMetadata> - <pluginExecutions> - <pluginExecution> - <pluginExecutionFilter> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-help-plugin</artifactId> - <versionRange>[2.1.1,)</versionRange> - <goals> - <goal>evaluate</goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore></ignore> - </action> - </pluginExecution> - </pluginExecutions> - </lifecycleMappingMetadata> - </configuration> - </plugin> - </plugins> - </pluginManagement> - </build> -</project> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>com.microsoft.windowsazure</groupId> + <artifactId>microsoft-windowsazure-api</artifactId> + <version>0.2.1</version> + <packaging>jar</packaging> + + <name>Microsoft Windows Azure Client API</name> + <description>API for Microsoft Windows Azure Clients</description> + <url>https://github.com/WindowsAzure/azure-sdk-for-java</url> + + <licenses> + <license> + <name>The Apache Software License, Version 2.0</name> + <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> + <distribution>repo</distribution> + </license> + </licenses> + + <scm> + <url>scm:git:https://github.com/WindowsAzure/azure-sdk-for-java</url> + <connection>scm:git:git://github.com/WindowsAzure/azure-sdk-for-java.git</connection> + </scm> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <legal><![CDATA[[INFO] Any downloads listed may be third party software. Microsoft grants you no rights for third party software.]]></legal> + </properties> + + <developers> + <developer> + <id>microsoft</id> + <name>Microsoft</name> + </developer> + </developers> + + <dependencies> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-client</artifactId> + <version>1.10-b02</version> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>2.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.8</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-all</artifactId> + <version>1.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-all</artifactId> + <scope>test</scope> + <version>1.9.0-rc1</version> + </dependency> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + <version>1</version> + </dependency> + <dependency> + <groupId>com.sun.jersey</groupId> + <artifactId>jersey-json</artifactId> + <version>1.10-b02</version> + </dependency> + <dependency> + <groupId>commons-logging</groupId> + <artifactId>commons-logging</artifactId> + <version>1.1.1</version> + </dependency> + <dependency> + <groupId>javax.mail</groupId> + <artifactId>mail</artifactId> + <version>1.4</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.1</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-help-plugin</artifactId> + <version>2.1.1</version> + <executions> + <execution> + <phase>validate</phase> + <goals> + <goal>evaluate</goal> + </goals> + <configuration> + <expression>legal</expression> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + </configuration> + </plugin> + <plugin> + <groupId>org.jvnet.jaxb2.maven2</groupId> + <artifactId>maven-jaxb2-plugin</artifactId> + <version>0.8.0</version> + <executions> + <execution> + <phase>generate-sources</phase> + <goals> + <goal>generate</goal> + </goals> + </execution> + </executions> + <configuration> + <extension>true</extension> + <plugins> + <plugin> + <groupId>org.jvnet.jaxb2_commons</groupId> + <artifactId>jaxb2-basics</artifactId> + <version>0.6.0</version> + </plugin> + <plugin> + <groupId>org.jvnet.jaxb2_commons</groupId> + <artifactId>jaxb2-basics-annotate</artifactId> + <version>0.6.0</version> + </plugin> + </plugins> + + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.8</version> + <configuration> + <excludePackageNames>*.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization</excludePackageNames> + <bottom><![CDATA[<code>/** +<br/>* Copyright 2011 Microsoft Corporation +<br/>* +<br/>* Licensed under the Apache License, Version 2.0 (the "License"); +<br/>* you may not use this file except in compliance with the License. +<br/>* You may obtain a copy of the License at +<br/>* http://www.apache.org/licenses/LICENSE-2.0 +<br/>* +<br/>* Unless required by applicable law or agreed to in writing, software +<br/>* distributed under the License is distributed on an "AS IS" BASIS, +<br/>* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +<br/>* See the License for the specific language governing permissions and +<br/>* limitations under the License. +<br/>*/</code>]]></bottom> + </configuration> + </plugin> + + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>findbugs-maven-plugin</artifactId> + <version>2.3.2</version> + <configuration> + <xmlOutput>true</xmlOutput> + <findbugsXmlOutput>true</findbugsXmlOutput> + <findbugsXmlWithMessages>true</findbugsXmlWithMessages> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-checkstyle-plugin</artifactId> + <version>2.8</version> + <configuration> + <configLocation>src/config/checkstyle.xml</configLocation> + </configuration> + </plugin> + + + </plugins> + <pluginManagement> + <plugins> + <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.--> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-help-plugin</artifactId> + <versionRange>[2.1.1,)</versionRange> + <goals> + <goal>evaluate</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore></ignore> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> +</project> From 34a9748c7989782044b69bd572955afb4b584cca Mon Sep 17 00:00:00 2001 From: unknown <joostden@JOOST-DESKTOP.redmond.corp.microsoft.com> Date: Wed, 11 Apr 2012 11:14:24 -0700 Subject: [PATCH 61/76] updating changelog for release --- ChangeLog.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.txt b/ChangeLog.txt index 79bfc2dca7940..b8521d51a60db 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,7 @@ +2012.04.11 Version 0.2.1 + * Added Service Layer support for Azure Table Storage + * Added Javadoc comments to Azure Queue Storage Service Layer + 2012.02.28 Version 0.2.0 * Added Support for Azure Table in com.microsoft.windowsazure.services.table * Added Client Tests for Table From faad5dc59ada38e1b68b54ab101db477ae98aac1 Mon Sep 17 00:00:00 2001 From: Joost de Nijs <joostden@microsoft.com> Date: Wed, 11 Apr 2012 11:29:09 -0700 Subject: [PATCH 62/76] Removing storage from Azure Table Storage and Azure Queue Storage --- ChangeLog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index b8521d51a60db..3d99a067228f4 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,6 +1,6 @@ 2012.04.11 Version 0.2.1 - * Added Service Layer support for Azure Table Storage - * Added Javadoc comments to Azure Queue Storage Service Layer + * Added Service Layer support for Azure Table + * Added Javadoc comments to Azure Queue Service Layer 2012.02.28 Version 0.2.0 * Added Support for Azure Table in com.microsoft.windowsazure.services.table From 74aaf66182641ef7a84f70baa0f2834354eba452 Mon Sep 17 00:00:00 2001 From: Joe Giardino <joegiard@microsoft.com> Date: Mon, 30 Apr 2012 13:15:35 -0700 Subject: [PATCH 63/76] Issue #Add URL Encoding for Keys in Table Batch Operations Signed-off-by: Joe Giardino <joegiard@microsoft.com> --- .../table/client/QueryTableOperation.java | 2 +- .../services/table/client/TableOperation.java | 25 +++++---- .../table/client/TableEscapingTests.java | 52 ++++++++++++++----- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java index 52dbf5ecdccb3..2f02f4bb50e23 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java @@ -183,7 +183,7 @@ public TableResult execute(final CloudTableClient client, final QueryTableOperat final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.query(client.getEndpoint(), tableName, - generateRequestIdentity(isTableEntry, operation.getPartitionKey()), + generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), options.getTimeoutIntervalInMs(), null/* Query Builder */, null/* Continuation Token */, options, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java index c9c72e0856ddc..8b8ec3e422bc7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java @@ -257,7 +257,7 @@ public TableResult execute(final CloudTableClient client, final TableOperation o final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.delete(client.getEndpoint(), tableName, - generateRequestIdentity(isTableEntry, tableIdentity), operation.getEntity().getEtag(), + generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); @@ -324,7 +324,7 @@ private TableResult performInsert(final CloudTableClient client, final String ta public TableResult execute(final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.insert(client.getEndpoint(), tableName, - generateRequestIdentity(isTableEntry, tableIdentity), + generateRequestIdentity(isTableEntry, tableIdentity, false), operation.opType != TableOperationType.INSERT ? operation.getEntity().getEtag() : null, operation.opType.getUpdateType(), options.getTimeoutIntervalInMs(), null, options, opContext); @@ -411,7 +411,7 @@ public TableResult execute(final CloudTableClient client, final TableOperation o final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.merge(client.getEndpoint(), tableName, - generateRequestIdentity(false, null), operation.getEntity().getEtag(), + generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); @@ -477,7 +477,7 @@ public TableResult execute(final CloudTableClient client, final TableOperation o final OperationContext opContext) throws Exception { final HttpURLConnection request = TableRequest.update(client.getEndpoint(), tableName, - generateRequestIdentity(false, null), operation.getEntity().getEtag(), + generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); @@ -577,10 +577,15 @@ else if (this.getOperationType() == TableOperationType.RETRIEVE) { * @param entryName * The entry name to use as the request identity if the <code>isSingleIndexEntry</code> parameter is * <code>true</code>. + * @param encodeKeys + * Pass <code>true</code> to url encode the partition & row keys * @return * A <code>String</code> containing the formatted request identity string. + * @throws StorageException + * If a storage service error occurred. */ - protected String generateRequestIdentity(boolean isSingleIndexEntry, final String entryName) { + protected String generateRequestIdentity(boolean isSingleIndexEntry, final String entryName, boolean encodeKeys) + throws StorageException { if (isSingleIndexEntry) { return String.format("'%s'", entryName); } @@ -602,22 +607,24 @@ protected String generateRequestIdentity(boolean isSingleIndexEntry, final Strin rk = this.getEntity().getRowKey(); } - return String.format("%s='%s',%s='%s'", TableConstants.PARTITION_KEY, pk, TableConstants.ROW_KEY, rk); + return String.format("%s='%s',%s='%s'", TableConstants.PARTITION_KEY, encodeKeys ? Utility.safeEncode(pk) + : pk, TableConstants.ROW_KEY, encodeKeys ? Utility.safeEncode(rk) : rk); } } /** * Reserved for internal use. Generates the request identity string for the specified table. The request identity * string combines the table name with the PartitionKey and RowKey from the operation to identify specific table - * entities. + * entities. This request identity is already UrlEncoded. * * @param tableName * A <code>String</code> containing the name of the table. * @return * A <code>String</code> containing the formatted request identity string for the specified table. + * @throws StorageException */ - protected String generateRequestIdentityWithTable(final String tableName) { - return String.format("/%s(%s)", tableName, generateRequestIdentity(false, null)); + protected String generateRequestIdentityWithTable(final String tableName) throws StorageException { + return String.format("/%s(%s)", tableName, generateRequestIdentity(false, null, true)); } /** diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java index ce90b73f1af4b..3926fd49aea6d 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableEscapingTests.java @@ -28,12 +28,12 @@ public class TableEscapingTests extends TableTestBase { @Test public void emptyString() throws StorageException { - doEscapeTest("", false); + doEscapeTest("", false, true); } @Test public void emptyStringBatch() throws StorageException { - doEscapeTest("", true); + doEscapeTest("", true, true); } @Test @@ -46,6 +46,18 @@ public void randomCharsBatch() throws StorageException { doEscapeTest("!$'\"()*+,;=", true); } + @Test + @Ignore + public void percent25() throws StorageException { + // Disabled Until Double Percent decoding issue is fixed for single entity operations + // doEscapeTest("foo%25", false, true); + } + + @Test + public void percent25Batch() throws StorageException { + doEscapeTest("foo%25", true, true); + } + @Test public void regularPKInQuery() throws StorageException { doQueryEscapeTest("data"); @@ -63,18 +75,18 @@ public void specialCharsBatch() throws StorageException { @Test public void unicode() throws StorageException { - doEscapeTest("\u00A9\u770b\u5168\u90e8", false); - doEscapeTest("char中文test", false); - doEscapeTest("char中文test", false); - doEscapeTest("世界你好", false); + doEscapeTest("\u00A9\u770b\u5168\u90e8", false, true); + doEscapeTest("char中文test", false, true); + doEscapeTest("char中文test", false, true); + doEscapeTest("世界你好", false, true); } @Test public void unicodeBatch() throws StorageException { - doEscapeTest("\u00A9\u770b\u5168\u90e8", true); - doEscapeTest("char中文test", true); - doEscapeTest("char中文test", true); - doEscapeTest("世界你好", true); + doEscapeTest("\u00A9\u770b\u5168\u90e8", true, true); + doEscapeTest("char中文test", true, true); + doEscapeTest("char中文test", true, true); + doEscapeTest("世界你好", true, true); } @Test @@ -87,12 +99,12 @@ public void unicodeInQuery() throws StorageException { @Test public void whiteSpaceOnly() throws StorageException { - doEscapeTest(" ", false); + doEscapeTest(" ", false, true); } @Test public void whiteSpaceOnlyBatch() throws StorageException { - doEscapeTest(" ", true); + doEscapeTest(" ", true, true); } @Test @@ -119,10 +131,14 @@ public void xmlTestBatch() throws StorageException { } private void doEscapeTest(String data, boolean useBatch) throws StorageException { + doEscapeTest(data, useBatch, false); + } + + private void doEscapeTest(String data, boolean useBatch, boolean includeInKey) throws StorageException { class1 ref = new class1(); ref.setA(data); - ref.setPartitionKey("temp"); + ref.setPartitionKey(includeInKey ? "temp" + data : "temp"); ref.setRowKey(UUID.randomUUID().toString()); if (useBatch) { TableBatchOperation batch = new TableBatchOperation(); @@ -147,6 +163,7 @@ private void doEscapeTest(String data, boolean useBatch) throws StorageException class1 retObj = res.getResultAsType(); Assert.assertEquals(ref.getA(), retObj.getA()); + Assert.assertEquals(ref.getPartitionKey(), retObj.getPartitionKey()); ref.setEtag(retObj.getEtag()); ref.setB(data); @@ -202,6 +219,15 @@ private void doEscapeTest(String data, boolean useBatch) throws StorageException Assert.assertEquals(ref.getA(), retObj.getA()); Assert.assertEquals(ref.getB(), retObj.getB()); Assert.assertEquals(ref.getC(), retObj.getC()); + + if (useBatch) { + TableBatchOperation batch = new TableBatchOperation(); + batch.delete(retObj); + res = tClient.execute(testSuiteTableName, batch).get(0); + } + else { + res = tClient.execute(testSuiteTableName, TableOperation.delete(retObj)); + } } private void doQueryEscapeTest(String data) throws StorageException { From d8ca51d0021856a785993010b66de6e536b91adf Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Tue, 27 Mar 2012 14:13:37 -0700 Subject: [PATCH 64/76] Fix Javadocs build warnings in ServiceBus --- .../serviceBus/models/BrokeredMessage.java | 20 +++++++++---------- .../serviceBus/models/ListQueuesResult.java | 18 ++++++++--------- .../serviceBus/models/ListRulesResult.java | 20 +++++++++---------- .../models/ListSubscriptionsResult.java | 20 +++++++++---------- .../serviceBus/models/ListTopicsResult.java | 20 +++++++++---------- 5 files changed, 49 insertions(+), 49 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/BrokeredMessage.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/BrokeredMessage.java index e149f01872e70..7f7e5caac0175 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/BrokeredMessage.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/BrokeredMessage.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.serviceBus.models; @@ -375,8 +375,8 @@ public String getTo() { /** * Sets the To recipient of the message. * - * @param A - * <code>String</code> object that represents the To recipient. + * @param to + * A <code>String</code> object that represents the To recipient. * * @return A <code>Message</code> object that represents the updated message. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListQueuesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListQueuesResult.java index df68d6fd58608..c06b5b3e27341 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListQueuesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListQueuesResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.serviceBus.models; @@ -36,7 +36,7 @@ public List<QueueInfo> getItems() { /** * Specfies the items in the result list. * - * @param value + * @param items * A <code>List</code> object that contains the {@link QueueInfo} objects assigned as the value of the * result. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListRulesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListRulesResult.java index 22cc18bb0d5fd..b12c3e65bb88a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListRulesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListRulesResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.serviceBus.models; @@ -34,9 +34,9 @@ public List<RuleInfo> getItems() { } /** - * Specfies the items in the result list. + * Specifies the items in the result list. * - * @param value + * @param items * A <code>List</code> object that contains the {@link RuleInfo} objects assigned as the value of the * result. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListSubscriptionsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListSubscriptionsResult.java index d77f40bfb519a..4b6c6cc5fbe86 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListSubscriptionsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListSubscriptionsResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.serviceBus.models; @@ -34,9 +34,9 @@ public List<SubscriptionInfo> getItems() { } /** - * Specfies the items in the result list. + * Specifies the items in the result list. * - * @param value + * @param items * A <code>List</code> object that contains the {@link SubscriptionInfo} objects assigned as the value of * the result. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListTopicsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListTopicsResult.java index 0aa49b51d9cf9..b5539a870055b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListTopicsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/serviceBus/models/ListTopicsResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.serviceBus.models; @@ -34,9 +34,9 @@ public List<TopicInfo> getItems() { } /** - * Specfies the items in the result list. + * Specifies the items in the result list. * - * @param value + * @param items * A <code>List</code> object that contains the {@link TopicInfo} objects assigned as the value of the * result. */ From c18be6f71e7dfdfc54ad5f8b12b94664da6f5f80 Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Tue, 27 Mar 2012 14:14:50 -0700 Subject: [PATCH 65/76] Fix Javadocs build warnings in Table Client --- .../services/table/client/CloudTableClient.java | 13 +++++-------- .../services/table/client/TableQuery.java | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java index b471594261c68..6ac9720aca758 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java @@ -954,7 +954,7 @@ public Iterable<String> listTables() { * account credentials of this instance. * * @param prefix - * A <String> containing the prefix to match on table names to return. + * A <code>String</code> containing the prefix to match on table names to return. * * @return * An <code>Iterable</code> collection of the table names in the storage account that match the specified @@ -977,7 +977,7 @@ public Iterable<String> listTables(final String prefix) { * operation. * * @param prefix - * A <String> containing the prefix to match on table names to return. + * A <code>String</code> containing the prefix to match on table names to return. * @param options * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout * settings for the operation. Specify <code>null</code> to use the request options specified on the @@ -1005,9 +1005,6 @@ public Iterable<String> listTables(final String prefix, final TableRequestOption * Tables</a> REST API to list the table names, using the Table service endpoint and storage account credentials of * this instance. * - * @param prefix - * A <String> containing the prefix to match on table names to return. - * * @return * A {@link ResultSegment} of <code>String</code> objects containing table names in the storage account. * @@ -1033,7 +1030,7 @@ public ResultSegment<String> listTablesSegmented() throws IOException, URISyntax * account credentials of this instance. * * @param prefix - * A <String> containing the prefix to match on table names to return. + * A <code>String</code> containing the prefix to match on table names to return. * * @return * A {@link ResultSegment} of <code>String</code> objects containing table names matching the prefix in the @@ -1066,7 +1063,7 @@ public ResultSegment<String> listTablesSegmented(final String prefix) throws IOE * operation. * * @param prefix - * A <String> containing the prefix to match on table names to return. + * A <code>String</code> containing the prefix to match on table names to return. * @param maxResults * The maximum number of table names to return in the {@link ResultSegment}. If this parameter is null, * the query will list up to the maximum 1,000 results. @@ -1105,7 +1102,7 @@ public ResultSegment<String> listTablesSegmented(final String prefix, final Inte * Reserved for internal use. Generates a query to list table names with the given prefix. * * @param prefix - * A <String> containing the prefix to match on table names to return. + * A <code>String</code> containing the prefix to match on table names to return. * @return * A {@link TableQuery} instance for listing table names with the specified prefix. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java index a54279ae0ce17..90a9bfa3501d3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableQuery.java @@ -456,7 +456,7 @@ public TableQuery() { * {@link TableServiceEntity}<code>.class</code> as the class type parameter if no more specialized type is * required. * - * @param tablename + * @param tableName * A <code>String</code> containing the name of the source table to query. * @param clazzType * The <code>java.lang.Class</code> of the class <code>T</code> that represents the table entity type for From c03202e687c67d29ad36ad94dd3f8213252568b0 Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Fri, 27 Apr 2012 17:00:41 -0700 Subject: [PATCH 66/76] Adding Javadocs comments for Blob service layer API, so far. --- .../services/blob/BlobConfiguration.java | 37 +- .../services/blob/BlobContract.java | 1288 ++++++++++++++++- .../services/blob/BlobService.java | 63 +- .../blob/models/AcquireLeaseOptions.java | 52 +- .../blob/models/AcquireLeaseResult.java | 46 +- .../services/blob/models/BlobProperties.java | 180 ++- .../blob/models/BlobServiceOptions.java | 42 +- .../services/blob/models/BlockList.java | 84 +- .../blob/models/CommitBlobBlocksOptions.java | 205 ++- .../services/blob/models/ContainerACL.java | 269 +++- .../services/blob/models/CopyBlobOptions.java | 164 ++- .../blob/models/CreateBlobBlockOptions.java | 73 +- .../blob/models/CreateBlobOptions.java | 306 +++- .../blob/models/CreateBlobPagesOptions.java | 27 +- .../blob/models/CreateBlobPagesResult.java | 99 +- .../models/CreateBlobSnapshotOptions.java | 104 +- .../blob/models/CreateBlobSnapshotResult.java | 88 +- .../blob/models/CreateContainerOptions.java | 109 +- .../blob/models/DeleteBlobOptions.java | 123 +- .../blob/models/DeleteContainerOptions.java | 57 +- .../blob/models/GetBlobMetadataOptions.java | 87 +- .../blob/models/GetBlobMetadataResult.java | 85 +- .../services/blob/models/GetBlobOptions.java | 157 +- .../blob/models/GetBlobPropertiesOptions.java | 87 +- .../blob/models/GetBlobPropertiesResult.java | 60 +- .../services/blob/models/GetBlobResult.java | 74 +- .../blob/models/GetContainerACLResult.java | 45 +- .../models/GetContainerPropertiesResult.java | 84 +- .../models/GetServicePropertiesResult.java | 47 +- .../blob/models/ListBlobBlocksOptions.java | 111 +- .../blob/models/ListBlobBlocksResult.java | 162 ++- .../blob/models/ListBlobRegionsOptions.java | 135 +- .../blob/models/ListBlobRegionsResult.java | 98 +- .../blob/models/ListBlobsOptions.java | 212 ++- .../services/blob/models/ListBlobsResult.java | 28 +- .../blob/models/ListContainersOptions.java | 128 +- .../blob/models/ListContainersResult.java | 268 +++- .../services/blob/models/PageRange.java | 92 +- .../blob/models/SetBlobMetadataOptions.java | 72 +- .../blob/models/SetBlobPropertiesOptions.java | 251 +++- .../blob/models/SetBlobPropertiesResult.java | 83 +- .../models/SetContainerMetadataOptions.java | 58 +- 42 files changed, 5503 insertions(+), 337 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobConfiguration.java index d561bcb73a87e..a8191bce65ae1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobConfiguration.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobConfiguration.java @@ -2,20 +2,41 @@ * Copyright 2011 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 + * 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. + * 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.blob; +/** + * This class contains static strings used to identify parts of a service configuration instance associated with the + * Windows Azure Blob service. + * <p> + * These values must not be altered. + */ public class BlobConfiguration { + /** + * The Blob configuration account name constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the value for the DNS prefix name for the storage account. + */ public final static String ACCOUNT_NAME = "blob.accountName"; + + /** + * The Blob configuration account key constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the value for the storage service account key. + */ public final static String ACCOUNT_KEY = "blob.accountKey"; + + /** + * The Blob configuration URI constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the URI value for the Blob storage service REST API address for the + * storage account. + */ public final static String URI = "blob.uri"; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java index e334771a68819..c6d2ec0d44332 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob; @@ -60,148 +60,1420 @@ import com.microsoft.windowsazure.services.core.FilterableService; import com.microsoft.windowsazure.services.core.ServiceException; +/** + * Defines the methods available on the Windows Azure blob storage service. Construct an object instance implementing + * <code>BlobContract</code> with one of the static <em>create</em> methods on {@link BlobService}. These methods + * associate a <code>Configuration</code> with the implementation, so the methods on the instance of + * <code>BlobContract</code> all work with a particular storage account. + */ public interface BlobContract extends FilterableService<BlobContract> { + /** + * Gets the service properties of the blob storage account. + * + * @return + * A {@link GetServicePropertiesResult} reference to the blob service properties. + * + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetServicePropertiesResult getServiceProperties() throws ServiceException; + /** + * Gets the service properties of the blob storage account, using the specified options. + * <p> + * Use the {@link BlobServiceOptions options} parameter to specify a server timeout for the operation. + * + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @return + * A {@link GetServicePropertiesResult} reference to the blob service properties. + * + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetServicePropertiesResult getServiceProperties(BlobServiceOptions options) throws ServiceException; + /** + * Sets the service properties of the blob storage account. + * + * @param serviceProperties + * A {@link ServiceProperties} instance containing the blob service properties to set. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException; + /** + * Sets the service properties of the blob storage account, using the specified options. + * <p> + * Use the {@link BlobServiceOptions options} parameter to specify the server timeout for the operation. + * + * @param serviceProperties + * A {@link ServiceProperties} instance containing the blob service properties to set. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setServiceProperties(ServiceProperties serviceProperties, BlobServiceOptions options) throws ServiceException; + /** + * Gets a list of the containers in the blob storage account. + * + * @return + * A {@link ListContainersResult} reference to the result of the list containers operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListContainersResult listContainers() throws ServiceException; + /** + * Gets a list of the containers in the blob storage account using the specified options. + * <p> + * Use the {@link ListContainersOptions options} parameter to specify options, including a server response timeout + * for the request, a container name prefix filter, a marker for continuing requests, the maximum number of results + * to return in a request, and whether to include container metadata in the results. + * + * @param options + * A {@link ListContainersOptions} instance containing options for the request. + * @return + * A {@link ListContainersResult} reference to the result of the list containers operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListContainersResult listContainers(ListContainersOptions options) throws ServiceException; + /** + * Creates a container with the specified name. + * <p> + * Container names must be unique within a storage account, and must follow the naming rules specified in <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135715.aspx">Naming and Referencing Containers, + * Blobs, and Metadata</a>. + * + * @param container + * A {@link String} containing the name of the container to create. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createContainer(String container) throws ServiceException; + /** + * Creates a container with the specified name, using the specified options. + * <p> + * Use the {@link CreateContainerOptions options} parameter to specify options, including a server response timeout + * for the request, metadata to set on the container, and the public access level for container and blob data. + * Container names must be unique within a storage account, and must follow the naming rules specified in <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135715.aspx">Naming and Referencing Containers, + * Blobs, and Metadata</a>. + * + * @param container + * A {@link String} containing the name of the container to create. + * @param options + * A {@link CreateContainerOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createContainer(String container, CreateContainerOptions options) throws ServiceException; + /** + * Marks a container for deletion. The container and any blobs contained within it are later deleted during garbage + * collection. + * <p> + * When a container is deleted, a container with the same name cannot be created for at least 30 seconds; the + * container may not be available for more than 30 seconds if the service is still processing the request. + * + * @param container + * A {@link String} containing the name of the container to delete. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteContainer(String container) throws ServiceException; + /** + * Marks a container for deletion, using the specified options. The container and any blobs contained within it are + * later deleted during garbage collection. + * <p> + * Use the {@link DeleteContainerOptions options} parameter to specify the server response timeout and any access + * conditions for the container deletion operation. Access conditions can be used to make the operation conditional + * on the value of the Etag or last modified time of the container. + * <p> + * When a container is deleted, a container with the same name cannot be created for at least 30 seconds; the + * container may not be available for more than 30 seconds if the service is still processing the request. + * + * @param container + * A {@link String} containing the name of the container to delete. + * @param options + * A {@link DeleteContainerOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteContainer(String container, DeleteContainerOptions options) throws ServiceException; + /** + * Gets all metadata and system properties for a container. + * + * @param container + * A {@link String} containing the name of the container to get properties and metadata for. + * @return + * A {@link GetContainerPropertiesResult} reference to the container properties and metadata. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerPropertiesResult getContainerProperties(String container) throws ServiceException; + /** + * Gets all metadata and system properties for a container, using the specified options. + * <p> + * Use the {@link BlobServiceOptions options} parameter to specify the server timeout for the operation. + * + * @param container + * A {@link String} containing the name of the container to get properties and metadata for. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @return + * A {@link GetContainerPropertiesResult} reference to the container properties and metadata. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerPropertiesResult getContainerProperties(String container, BlobServiceOptions options) throws ServiceException; + /** + * Gets all metadata for a container. + * + * @param container + * A {@link String} containing the name of the container to get the metadata from. + * @return + * A {@link GetContainerPropertiesResult} reference to the container metadata. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerPropertiesResult getContainerMetadata(String container) throws ServiceException; + /** + * Gets all metadata for a container, using the specified options. + * <P> + * Use the {@link BlobServiceOptions options} parameter to specify the server timeout for the operation. + * + * @param container + * A {@link String} containing the name of the container to get the metadata from. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @return + * A {@link GetContainerPropertiesResult} reference to the container metadata. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerPropertiesResult getContainerMetadata(String container, BlobServiceOptions options) throws ServiceException; + /** + * Gets the public access level and container-level access policies for a container. + * + * @param container + * A {@link String} containing the name of the container to get the public access level and + * container-level access policies from. + * @return + * A {@link GetContainerACLResult} reference to the container's public access level and + * container-level access policies. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerACLResult getContainerACL(String container) throws ServiceException; + /** + * Gets the public access level and container-level access policies for a container, using the specified + * options. + * <p> + * Use the {@link BlobServiceOptions options} parameter to specify the server timeout for the operation. + * + * @param container + * A {@link String} containing the name of the container to get the public access level and + * container-level access policies from. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @return + * A {@link GetContainerACLResult} reference to the container's public access level and + * container-level access policies. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetContainerACLResult getContainerACL(String container, BlobServiceOptions options) throws ServiceException; + /** + * Sets the public access level and container-level access policies for a container. + * + * @param container + * A {@link String} containing the name of the container to set the public access level and + * container-level access policies on. + * @param acl + * A {@link ContainerACL} instance containing the public access level and container-level access policies + * to set on the container. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setContainerACL(String container, ContainerACL acl) throws ServiceException; + /** + * Sets the public access level and container-level access policies for a container, using the specified options. + * <p> + * Use the {@link BlobServiceOptions options} parameter to specify the server timeout for the operation. + * + * @param container + * A {@link String} containing the name of the container to set the public access level and + * container-level access policies on. + * @param acl + * A {@link ContainerACL} instance containing the public access level and container-level access policies + * to set on the container. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setContainerACL(String container, ContainerACL acl, BlobServiceOptions options) throws ServiceException; + /** + * Sets the metadata on a container. + * <p> + * Calling this method overwrites all existing metadata that is associated with the container. It is not possible to + * modify an individual name-value pair. To keep the existing metadata and update or add individual values, make a + * copy of the metadata, make your changes, and upload the modified copy. Calling this method also updates the Etag + * and Last-Modified-Time properties for the container. + * <p> + * Metadata for a container can also be set at the time it is created. + * + * @param container + * A {@link String} containing the name of the container to set metadata on. + * @param metadata + * A {@link java.util.HashMap} of pairs of {@link String} containing the metadata name-value pairs to set + * on the container. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setContainerMetadata(String container, HashMap<String, String> metadata) throws ServiceException; + /** + * Sets the metadata on a container, using the specified options. + * <p> + * Use the {@link SetContainerMetadataOptions options} parameter to specify the server timeout for the operation, + * and to set access conditions on whether to update the metadata or not. + * <p> + * Calling this method overwrites all existing metadata that is associated with the container. It is not possible to + * modify an individual name-value pair. To keep the existing metadata and update or add individual values, make a + * copy of the metadata, make your changes, and upload the modified copy. Calling this method also updates the ETag + * and last modified time properties for the container. + * <p> + * Metadata for a container can also be set at the time it is created. + * + * @param container + * A {@link String} containing the name of the container to set metadata on. + * @param metadata + * A {@link java.util.HashMap} of pairs of {@link String} containing the metadata name-value pairs to set + * on the container. + * @param options + * A {@link SetContainerMetadataOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setContainerMetadata(String container, HashMap<String, String> metadata, SetContainerMetadataOptions options) throws ServiceException; + /** + * Lists the blobs in a container. + * + * @param container + * A {@link String} containing the name of the container of blobs to list. + * @return + * A {@link ListBlobsResult} reference to the result of the list blobs operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobsResult listBlobs(String container) throws ServiceException; + /** + * Lists the blobs in a container, using the specified options. + * <p> + * Use the {@link ListBlobsOptions options} parameter to optionally specify the server timeout for the operation, a + * prefix for blobs to match, a marker to continue a list operation, a maximum number of results to return with one + * list operation, a delimiter for structuring virtual blob hierarchies, and whether to include blob metadata, blob + * snapshots, and uncommitted blobs in the results. + * + * @param container + * A {@link String} containing the name of the container of blobs to list. + * @param options + * A {@link ListBlobsOptions} instance containing options for the request. + * @return + * A {@link ListBlobsResult} reference to the result of the list blobs operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobsResult listBlobs(String container, ListBlobsOptions options) throws ServiceException; + /** + * Creates a page blob of the specified maximum length. + * <p> + * Note that this method only initializes the page blob. To add content to a page blob, use the + * {@link BlobContract#createBlobPages(String, String, PageRange, long, InputStream)} or + * {@link BlobContract#createBlobPages(String, String, PageRange, long, InputStream, CreateBlobPagesOptions)} + * methods. + * + * @param container + * A {@link String} containing the name of the container to create the blob in. + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param length + * The length in bytes of the page blob to create. The length must be a multiple of 512 and may be up to + * 1 TB. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createPageBlob(String container, String blob, int length) throws ServiceException; + /** + * Creates a page blob of the specified maximum length, using the specified options. + * <p> + * Use the {@link CreateBlobOptions options} parameter to optionally specify the server timeout for the operation, + * the MIME content type and content encoding for the blob, the content language, the MD5 hash, a cache control + * value, blob metadata, and a sequence number. + * <p> + * Note that this method only initializes the blob. To add content to a page blob, use the + * {@link BlobContract#createBlobPages(String, String, PageRange, long, InputStream)} or + * {@link BlobContract#createBlobPages(String, String, PageRange, long, InputStream, CreateBlobPagesOptions)} + * methods. + * + * @param container + * A {@link String} containing the name of the container to create the blob in. + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param length + * The length in bytes of the page blob to create. The length must be a multiple of 512 and may be up to + * 1 TB. + * @param options + * A {@link CreateBlobOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createPageBlob(String container, String blob, int length, CreateBlobOptions options) throws ServiceException; + /** + * Creates a block blob from a content stream. + * + * @param container + * A {@link String} containing the name of the container to create the blob in. + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param contentStream + * An {@link InputStream} reference to the content stream to upload to the new blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createBlockBlob(String container, String blob, InputStream contentStream) throws ServiceException; + /** + * Creates a block blob from a content stream, using the specified options. + * <p> + * Use the {@link CreateBlobOptions options} parameter to optionally specify the server timeout for the operation, + * the MIME content type and content encoding for the blob, the content language, the MD5 hash, a cache control + * value, and blob metadata. + * + * @param container + * A {@link String} containing the name of the container to create the blob in. + * @param blob + * A {@link String} containing the name of the blob to create. A blob name can contain any combination of + * characters, but reserved URL characters must be properly escaped. A blob name must be at least one + * character long and cannot be more than 1,024 characters long, and must be unique within the container. + * @param contentStream + * An {@link InputStream} reference to the content to upload to the new blob. + * @param options + * A {@link CreateBlobOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createBlockBlob(String container, String blob, InputStream contentStream, CreateBlobOptions options) throws ServiceException; + /** + * Clears a range of pages from a page blob. + * <p> + * This method clears and releases the storage for the page range specified by the <em>range</em> parameter within + * the page blob specified by the <em>blob</em> and <em>container</em> parameters. The <em>range</em> parameter can + * specify up to the value of the blob's full size. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to clear the range from. + * @param range + * A {@link PageRange} containing the start and end of the range to clear in the page blob. + * @return + * A {@link CreateBlobPagesResult} reference to the result of the clear blob pages operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobPagesResult clearBlobPages(String container, String blob, PageRange range) throws ServiceException; + /** + * Clears a range of pages from a page blob, using the specified options. + * <p> + * This method clears and releases the storage for the page range specified by the <em>range</em> parameter within + * the page blob specified by the <em>blob</em> and <em>container</em> parameters. The <em>range</em> parameter can + * specify up to the value of the blob's full size. Use the {@link CreateBlobPagesOptions options} parameter to + * optionally specify the server timeout for the operation, the lease ID if the blob has an active lease, and access + * conditions for the clear pages operation. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to clear the range from. + * @param range + * A {@link PageRange} containing the start and end of the range to clear in the page blob. + * @param options + * A {@link CreateBlobPagesOptions} instance containing options for the request. + * @return + * A {@link CreateBlobPagesResult} reference to the result of the clear blob pages operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobPagesResult clearBlobPages(String container, String blob, PageRange range, CreateBlobPagesOptions options) throws ServiceException; + /** + * Creates or updates a range of pages in a page blob. + * <p> + * This method creates or updates the storage for the page range specified by the <em>range</em> parameter within + * the page blob specified by the <em>blob</em> and <em>container</em> parameters. The <em>range</em> parameter can + * specify up to 4MB of data, and its length must match the <em>length</em> parameter. The operation stores + * <em>length</em> bytes from the <em>contentStream</em> parameter in the specified page range in the blob. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create or update the range content of. + * @param range + * A {@link PageRange} containing the start and end of the range to create or update in the page blob. + * @param length + * The number of bytes to read from the <em>contentStream</em> parameter into the specified range. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the page blob. + * @return + * A {@link CreateBlobPagesResult} reference to the result of the create blob pages operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobPagesResult createBlobPages(String container, String blob, PageRange range, long length, InputStream contentStream) throws ServiceException; + /** + * Creates or updates a range of pages in a page blob, using the specified options. + * <p> + * This method creates or updates the storage for the page range specified by the <em>range</em> parameter within + * the page blob specified by the <em>blob</em> and <em>container</em> parameters. The <em>range</em> parameter can + * specify up to 4MB of data, and its length must match the <em>length</em> parameter. The operation stores + * <em>length</em> bytes from the <em>contentStream</em> parameter in the specified page range in the blob. Use the + * {@link CreateBlobPagesOptions options} parameter to optionally specify the server timeout for the operation, the + * lease ID if the blob has an active lease, an MD5 hash of the content for verification, and access conditions for + * the create pages operation. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create or update the range content of. + * @param range + * A {@link PageRange} containing the start and end of the range to create or update in the page blob. + * @param length + * The number of bytes to read from the <em>contentStream</em> parameter into the specified range. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the page blob. + * @param options + * A {@link CreateBlobPagesOptions} instance containing options for the request. + * @return + * A {@link CreateBlobPagesResult} reference to the result of the create blob pages operation. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobPagesResult createBlobPages(String container, String blob, PageRange range, long length, InputStream contentStream, CreateBlobPagesOptions options) throws ServiceException; + /** + * Creates a new uncommited block from a content stream. + * <p> + * This method creates an uncommitted block for a block blob specified by the <em>blob</em> and <em>container</em> + * parameters. The <em>blockId</em> parameter is a client-specified ID for the block, which must be less than or + * equal to 64 bytes in size. For a given blob, the length of the value specified for the <em>blockId</em> parameter + * must be the same size for each block. The <em>contentStream</em> parameter specifies the content to be copied to + * the block. The content for the block must be less than or equal to 4 MB in size. + * <p> + * To create or update a block blob, the blocks that have been successfully written to the server with this method + * must be committed using a call to {@link BlobContract#commitBlobBlocks(String, String, BlockList)} or + * {@link BlobContract#commitBlobBlocks(String, String, BlockList, CommitBlobBlocksOptions)}. + * + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create the block for. + * @param blockId + * A {@link String} containing a client-specified ID for the block. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the block. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createBlobBlock(String container, String blob, String blockId, InputStream contentStream) throws ServiceException; + /** + * Creates a new uncommitted block from a content stream, using the specified options. + * <p> + * This method creates an uncommitted block for a block blob specified by the <em>blob</em> and <em>container</em> + * parameters. The <em>blockId</em> parameter is a client-specified ID for the block, which must be less than or + * equal to 64 bytes in size. For a given blob, the length of the value specified for the <em>blockId</em> parameter + * must be the same size for each block. The <em>contentStream</em> parameter specifies the content to be copied to + * the block. The content for the block must be less than or equal to 4 MB in size. Use the + * {@link CreateBlobBlockOptions options} parameter to optionally specify the server timeout for the operation, the + * lease ID if the blob has an active lease, and the MD5 hash value for the block content. + * <p> + * To create or update a block blob, the blocks that have been successfully written to the server with this method + * must be committed using a call to {@link BlobContract#commitBlobBlocks(String, String, BlockList)} or + * {@link BlobContract#commitBlobBlocks(String, String, BlockList, CommitBlobBlocksOptions)}. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create the block for. + * @param blockId + * A {@link String} containing a client-specified ID for the block. + * @param contentStream + * An {@link InputStream} reference to the content to copy to the block. + * @param options + * A {@link CreateBlobBlockOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createBlobBlock(String container, String blob, String blockId, InputStream contentStream, CreateBlobBlockOptions options) throws ServiceException; + /** + * Commits a list of blocks to a block blob. + * <p> + * This method creates or updates the block blob specified by the <em>blob</em> and <em>container</em> parameters. + * You can call this method to update a blob by uploading only those blocks that have changed, then committing the + * new and existing blocks together. You can do this with the <em>blockList</em> parameter by specifying whether to + * commit a block from the committed block list or from the uncommitted block list, or to commit the most recently + * uploaded version of the block, whichever list it may belong to. + * <p> + * In order to be written as part of a blob, each block in the list must have been successfully written to the + * server with a call to {@link BlobContract#createBlobBlock(String, String, String, InputStream)} or + * {@link BlobContract#createBlobBlock(String, String, String, InputStream, CreateBlobBlockOptions)}. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the block blob to create or update. + * @param blockList + * A {@link BlockList} containing the list of blocks to commit to the block blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void commitBlobBlocks(String container, String blob, BlockList blockList) throws ServiceException; + /** + * Commits a block list to a block blob, using the specified options. + * <p> + * This method creates or updates the block blob specified by the <em>blob</em> and <em>container</em> parameters. + * You can call this method to update a blob by uploading only those blocks that have changed, then committing the + * new and existing blocks together. You can do this with the <em>blockList</em> parameter by specifying whether to + * commit a block from the committed block list or from the uncommitted block list, or to commit the most recently + * uploaded version of the block, whichever list it may belong to. Use the + * {@link BlobContract#CommitBlobBlocksOptions options} parameter to optionally specify the server timeout for the + * operation, the MIME content type and content encoding for the blob, the content language, the MD5 hash, a cache + * control value, blob metadata, the lease ID if the blob has an active lease, and any access conditions for the + * operation. + * <p> + * In order to be written as part of a blob, each block in the list must have been successfully written to the + * server with a call to {@link BlobContract#createBlobBlock(String, String, String, InputStream)} or + * {@link BlobContract#createBlobBlock(String, String, String, InputStream, CreateBlobBlockOptions)}. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the block blob to create or update. + * @param blockList + * A {@link BlockList} containing the list of blocks to commit to the block blob. + * @param options + * A {@link CommitBlobBlocksOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void commitBlobBlocks(String container, String blob, BlockList blockList, CommitBlobBlocksOptions options) throws ServiceException; + /** + * Lists the blocks of a blob. + * <p> + * This method lists the committed blocks of the block blob specified by the <em>blob</em> and <em>container</em> + * parameters. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the block blob to list. + * @return + * A {@link ListBlobBlocksResult} instance containing the list of blocks returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobBlocksResult listBlobBlocks(String container, String blob) throws ServiceException; + /** + * Lists the blocks of a blob, using the specified options. + * <p> + * This method lists the committed blocks, uncommitted blocks, or both, of the block blob specified by the + * <em>blob</em> and <em>container</em> parameters. Use the {@link ListBlobBlocksOptions options} parameter to + * specify an optional server timeout for the operation, the lease ID if the blob has an active lease, the snapshot + * timestamp to get the committed blocks of a snapshot, whether to return the committed block list, and whether to + * return the uncommitted block list. By default, only the committed blocks of the blob are returned. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the block blob to list. + * @param options + * A {@link ListBlobBlocksOptions} instance containing options for the request. + * @return + * A {@link ListBlobBlocksResult} instance containing the list of blocks returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobBlocksResult listBlobBlocks(String container, String blob, ListBlobBlocksOptions options) throws ServiceException; + /** + * Gets the properties of a blob. + * <p> + * This method lists the properties of the blob specified by the <em>blob</em> and <em>container</em> parameters. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get properties for. + * @return + * A {@link GetBlobPropertiesResult} instance containing the blob properties returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobPropertiesResult getBlobProperties(String container, String blob) throws ServiceException; + /** + * Gets the properties of a blob, using the specified options. + * <p> + * This method lists the properties of the blob specified by the <em>blob</em> and <em>container</em> parameters. + * Use the {@link GetBlobPropertiesOptions options} parameter to set an optional server timeout for the operation, + * the lease ID if the blob has an active lease, the snapshot timestamp to get the properties of a snapshot, and any + * access conditions for the request. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get properties for. + * @param options + * A {@link GetBlobPropertiesOptions} instance containing options for the request. + * @return + * A {@link GetBlobPropertiesResult} instance containing the blob properties returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobPropertiesResult getBlobProperties(String container, String blob, GetBlobPropertiesOptions options) throws ServiceException; + /** + * Gets the metadata of a blob. + * <p> + * This method lists the user-specified metadata of the blob specified by the <em>blob</em> and <em>container</em> + * parameters. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get metadata for. + * @return + * A {@link GetBlobMetadataResult} instance containing the blob metadata returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobMetadataResult getBlobMetadata(String container, String blob) throws ServiceException; + /** + * Gets the metadata of a blob, using the specified options. + * <p> + * This method lists the user-specified metadata of the blob specified by the <em>blob</em> and <em>container</em> + * parameters. Use the {@link GetBlobMetadataOptions options} parameter to set an optional server timeout for the + * operation, the lease ID if the blob has an active lease, the snapshot timestamp to get the properties of a + * snapshot, and any access conditions for the request. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get metadata for. + * @param options + * A {@link GetBlobMetadataOptions} instance containing options for the request. + * @return + * A {@link GetBlobMetadataResult} instance containing the blob metadata returned for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobMetadataResult getBlobMetadata(String container, String blob, GetBlobMetadataOptions options) throws ServiceException; + /** + * Gets the list of valid page ranges for a page blob. + * <p> + * This method lists the valid page ranges of the page blob specified by the <em>blob</em> and <em>container</em> + * parameters. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get page ranges for. + * @return + * A {@link ListBlobRegionsResult} instance containing the valid page ranges of the blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobRegionsResult listBlobRegions(String container, String blob) throws ServiceException; + /** + * Gets a list of valid page ranges for a page blob or snapshot of a page blob, using the specified options. + * <p> + * This method lists the valid page ranges of the page blob specified by the <em>blob</em> and <em>container</em> + * parameters. Use the {@link ListBlobRegionsOptions options} parameter to set an optional server timeout for the + * operation, the lease ID if the blob has an active lease, the snapshot timestamp to get the valid page ranges of a + * snapshot, the start offset and/or end offset to use to narrow the returned valid page range results, and any + * access conditions for the request. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get page ranges for. + * @param options + * A {@link ListBlobRegionsOptions} instance containing options for the request. + * @return + * A {@link ListBlobRegionsResult} instance containing the valid page ranges of the blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ ListBlobRegionsResult listBlobRegions(String container, String blob, ListBlobRegionsOptions options) throws ServiceException; + /** + * Sets the specified properties on a blob. + * <p> + * This method sets properties on the blob specified by the <em>blob</em> and <em>container</em> parameters. Use the + * {@link SetBlobPropertiesOptions options} parameter to set an optional server timeout for the operation, the MIME + * content type and content encoding for the blob, the content length, the content language, the MD5 hash, a cache + * control value, a blob lease ID, a sequence number and sequence number action value, and any access conditions for + * the operation. + * <p> + * A page blob's sequence number is updated only if the request meets either of the following conditions: + * <ul> + * <li>The request sets the sequence number action to <code>max</code> or <code>update</code>, and also specifies a + * value for the sequence number.</li> + * <li>The request sets the sequence number action to <code>increment</code>, indicating that the service should + * increment the sequence number by one.</li> + * </ul> + * <p> + * A page blob's size is modified only if the request specifies a value for the content length. + * <p> + * If a request sets only a sequence number and/or content length, and no other properties, then none of the blob's + * other properties are modified. + * <p> + * If any one or more of the following properties is set in the request, then all of these properties are set + * together. If a value is not provided for a given property when at least one of the properties listed below is + * set, then that property will be cleared for the blob. + * <ul> + * <li>cache control</li> + * <li>content type</li> + * <li>MD5 hash value</li> + * <li>content encoding</li> + * <li>content language</li> + * </ul> + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to set properties on. + * @param options + * A {@link SetBlobPropertiesOptions} instance containing options for the request. + * @return + * A {@link SetBlobPropertiesResult} instance containing the ETag and last modified time of the blob, and + * the sequence number, if applicable. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ SetBlobPropertiesResult setBlobProperties(String container, String blob, SetBlobPropertiesOptions options) throws ServiceException; + /** + * Sets user-specified metadata on a blob. + * <p> + * This method sets the metadata specified in the <em>metadata</em> parameter on the blob specified by the + * <em>blob</em> and <em>container</em> parameters. + * <p> + * Blob metadata is a collection of name-value {@link String} pairs for client use and is opaque to the server. This + * request replaces all existing metadata associated with the blob. To remove all metadata from the blob, make this + * request with an empty metadata list. To replace or append a single metadata name-value pair, get the existing + * blob metadata and replace or append the new item, then make this request with the updated metadata. + * <p> + * Metadata names must adhere to the naming rules for <a + * href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to set metadata on. + * @param metadata + * A {@link HashMap} of key-value pairs of {@link String} containing the blob metadata to set. + * @return + * A {@link SetBlobMetadataResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ SetBlobMetadataResult setBlobMetadata(String container, String blob, HashMap<String, String> metadata) throws ServiceException; + /** + * Sets user-specified metadata on a blob, using the specified options. + * <p> + * This method sets the metadata specified in the <em>metadata</em> parameter on the blob specified by the + * <em>blob</em> and <em>container</em> parameters. Use the {@link SetBlobMetadataOptions options} parameter to set + * an optional server timeout for the operation, a blob lease ID, and any access conditions for the operation. + * <p> + * Blob metadata is a collection of name-value {@link String} pairs for client use and is opaque to the server. This + * request replaces all existing metadata associated with the blob. To remove all metadata from the blob, make this + * request with an empty metadata list. To replace or append a single metadata name-value pair, get the existing + * blob metadata and replace or append the new item, then make this request with the updated metadata. + * <p> + * Metadata names must adhere to the naming rules for <a + * href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to set metadata on. + * @param metadata + * A {@link HashMap} of key-value pairs of {@link String} containing the blob metadata to set. + * @param options + * A {@link SetBlobMetadataOptions} instance containing options for the request. + * @return + * A {@link SetBlobMetadataResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ SetBlobMetadataResult setBlobMetadata(String container, String blob, HashMap<String, String> metadata, SetBlobMetadataOptions options) throws ServiceException; + /** + * Gets the properties, metadata, and content of a blob. + * <p> + * This method gets the properties, metadata, and content of the blob specified by the <em>blob</em> and + * <em>container</em> parameters. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get. + * @return + * A {@link GetBlobResult} instance containing the properties, metadata, and content of the blob from the + * server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobResult getBlob(String container, String blob) throws ServiceException; + /** + * Gets the properties, metadata, and content of a blob or blob snapshot, using the specified options. + * <p> + * This method gets the properties, metadata, and content of the blob specified by the <em>blob</em> and + * <em>container</em> parameters. Use the {@link GetBlobOptions options} parameter to set an optional server timeout + * for the operation, a snapshot timestamp to specify a snapshot, a blob lease ID to get a blob with an active + * lease, an optional start and end range for blob content to return, and any access conditions to satisfy. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to get. + * @param options + * A {@link GetBlobOptions} instance containing options for the request. + * @return + * A {@link GetBlobResult} instance containing the properties, metadata, and content of the blob from the + * server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetBlobResult getBlob(String container, String blob, GetBlobOptions options) throws ServiceException; + /** + * Marks a blob for deletion. + * <p> + * This method marks the properties, metadata, and content of the blob specified by the <em>blob</em> and + * <em>container</em> parameters for deletion. + * <p> + * When a blob is successfully deleted, it is immediately removed from the storage account's index and is no longer + * accessible to clients. The blob's data is later removed from the service during garbage collection. + * <p> + * Note that in order to delete a blob, you must delete all of its snapshots. You can delete an individual snapshot, + * only the snapshots, or both the blob and its snapshots with the + * {@link #deleteBlob(String, String, DeleteBlobOptions)} method. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to delete. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteBlob(String container, String blob) throws ServiceException; + /** + * Marks a blob or snapshot for deletion, using the specified options. + * <p> + * This method marks the properties, metadata, and content of the blob or snapshot specified by the <em>blob</em> + * and <em>container</em> parameters for deletion. Use the {@link DeleteBlobOptions options} parameter to set an + * optional server timeout for the operation, a snapshot timestamp to specify an individual snapshot to delete, a + * blob lease ID to delete a blob with an active lease, a flag indicating whether to delete all snapshots but not + * the blob, or both the blob and all snapshots, and any access conditions to satisfy. + * <p> + * When a blob is successfully deleted, it is immediately removed from the storage account's index and is no longer + * accessible to clients. The blob's data is later removed from the service during garbage collection. + * <p> + * If the blob has an active lease, the client must specify a valid lease ID in the <em>options</em> parameter in + * order to delete it. + * <p> + * If a blob has a large number of snapshots, it's possible that the delete blob operation will time out. If this + * happens, the client should retry the request. + * + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to delete. + * @param options + * A {@link DeleteBlobOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteBlob(String container, String blob, DeleteBlobOptions options) throws ServiceException; + /** + * Creates a read-only snapshot of a blob. + * <p> + * This method creates a read-only snapshot of the blob specified by the <em>blob</em> and <em>container</em> + * parameters as of the time the server processes the request. The current properties and metadata of the blob are + * copied to the snapshot. The base blob's committed block list is also copied to the snapshot, if the blob is a + * block blob. Any uncommitted blocks are not copied. Once a snapshot has been created, it can be read, copied, or + * deleted, but not modified. + * <p> + * Use the {@link #createBlobSnapshot(String, String, CreateBlobSnapshotOptions)} overload of this method to make a + * create blob snapshot request of a blob with an active lease, to change the metadata on the snapshot, or to set + * access conditions on the request. + * <p> + * When you create a snapshot, the Blob service returns a timestamp value that uniquely identifies the snapshot + * relative to its base blob. You can use this value to perform further operations on the snapshot. Any method on a + * blob that is valid for a snapshot can be called by specifying the snapshot timestamp as an option. Note that you + * should treat this timestamp value as opaque. + * <p> + * A snapshot provides a convenient way to back up blob data. You can use a snapshot to restore a blob to an earlier + * version by calling {@link #copyBlob(String, String, String, String, CopyBlobOptions) copyBlob} to overwrite a + * base blob with its snapshot. The snapshot remains, but the base blob is overwritten with a copy that can be both + * read and written. + * <p> + * Note that each time you make a create blob snapshot request, a new snapshot is created, with a unique timestamp + * value. A blob can support any number of snapshots. Existing snapshots are never overwritten, but must be deleted + * explicitly with a {@link #deleteBlob(String, String, DeleteBlobOptions) deleteBlob} request. + * <p> + * Note that a lease associated with the base blob is not copied to the snapshot. Snapshots cannot be leased. + * <p> + * When a base blob is copied with a {@link #copyBlob(String, String, String, String, CopyBlobOptions) copyBlob} + * request, any snapshots of the base blob are not copied to the destination blob. When a destination blob is + * overwritten with a copy, any snapshots associated with the destination blob stay intact under its name. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create a snapshot of. + * @return + * A {@link CreateBlobSnapshotResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobSnapshotResult createBlobSnapshot(String container, String blob) throws ServiceException; + /** + * Creates a read-only snapshot of a blob, using the specified options. + * <p> + * This method creates a read-only snapshot of the blob specified by the <em>blob</em> and <em>container</em> + * parameters as of the time the server processes the request. Use the {@link CreateBlobSnapshotOptions options} + * parameter to set an optional server timeout for the operation, to make a create blob snapshot request of a blob + * with an active lease, to change the metadata on the snapshot, or to set access conditions on the request. Unless + * overridden in the <em>options</em> parameter, the current properties and metadata of the blob are copied to the + * snapshot. The base blob's committed block list is also copied to the snapshot, if the blob is a block blob. Any + * uncommitted blocks are not copied. Once a snapshot has been created, it can be read, copied, or deleted, but not + * modified. + * <p> + * When you create a snapshot, the Blob service returns a timestamp value that uniquely identifies the snapshot + * relative to its base blob. You can use this value to perform further operations on the snapshot. Any method on a + * blob that is valid for a snapshot can be called by specifying the snapshot timestamp as an option. Note that you + * should treat this timestamp value as opaque. + * <p> + * A snapshot provides a convenient way to back up blob data. You can use a snapshot to restore a blob to an earlier + * version by calling {@link #copyBlob(String, String, String, String, CopyBlobOptions) copyBlob} to overwrite a + * base blob with its snapshot. The snapshot remains, but the base blob is overwritten with a copy that can be both + * read and written. + * <p> + * Note that each time you make a create blob snapshot request, a new snapshot is created, with a unique timestamp + * value. A blob can support any number of snapshots. Existing snapshots are never overwritten, but must be deleted + * explicitly with a {@link #deleteBlob(String, String, DeleteBlobOptions) deleteBlob} request. + * <p> + * Note that a lease associated with the base blob is not copied to the snapshot. Snapshots cannot be leased. + * <p> + * When a base blob is copied with a {@link #copyBlob(String, String, String, String, CopyBlobOptions) copyBlob} + * request, any snapshots of the base blob are not copied to the destination blob. When a destination blob is + * overwritten with a copy, any snapshots associated with the destination blob stay intact under its name. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to create a snapshot of. + * @param options + * A {@link CreateBlobSnapshotOptions} instance containing options for the request. + * @return + * A {@link CreateBlobSnapshotResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ CreateBlobSnapshotResult createBlobSnapshot(String container, String blob, CreateBlobSnapshotOptions options) throws ServiceException; + /** + * Copies a source blob to a destination within the storage account. + * <p> + * This method creates a copy of the properties, metadata, and content of the blob specified by the + * <em>sourceBlob</em> and <em>sourceContainer</em> in the destination specified by the <em>destinationBlob</em> and + * <em>destinationContainer</em> parameters. The <em>sourceContainer</em> and <em>destinationContainer</em> + * parameters may be empty to specify the root container. The destination may be a new or existing blob. + * <p> + * Use the {@link #copyBlob(String, String, String, String, CopyBlobOptions)} overload of this method to copy a + * snapshot, modify the metadata of the destination, copy from or to a blob with an active lease, or to set access + * conditions on either the source or the destination blob. + * <p> + * The source blob for a copy operation may be a block blob or a page blob, or a snapshot of either. If the + * destination blob already exists, it must be of the same blob type as the source blob. + * <p> + * Copying a source blob always copies the entire blob; copying a range of bytes or a set of blocks is not + * supported. + * <p> + * A copy blob request can take any of the following forms: + * <ul> + * <li>You can copy a source blob to a destination blob with a different name from that of the source blob. The + * destination blob can be an existing blob, or a new blob created by the copy operation.</li> + * <li>You can copy a source blob to a destination blob with the same name, effectively replacing the source blob. + * Such a copy operation removes any uncommitted blocks and overwrites the blob's metadata.</li> + * <li> + * You can copy a snapshot over its base blob. By promoting a snapshot to the position of the base blob, you can + * restore an earlier version of a blob.</li> + * <li> + * You can copy a snapshot to a destination blob with a different name. The resulting destination blob is a + * writeable blob and not a snapshot.</li> + * </ul> + * <p> + * The source blob's committed block list is also copied to the destination blob, if the blob is a block blob. Any + * uncommitted blocks are not copied. + * <p> + * The destination blob is always the same size as the source blob, so the value of the Content-Length header for + * the destination blob will be the same as that for the source blob. + * <p> + * When the source blob and destination blob are the same, Copy Blob removes any uncommitted blocks. + * <p> + * When a source blob is copied, any snapshots of the source blob are not copied to the destination. When a + * destination blob is overwritten with a copy, any snapshots associated with the destination blob stay intact under + * its name. + * + * @param destinationContainer + * A {@link String} containing the name of the destination blob's container. + * @param destinationBlob + * A {@link String} containing the name of the destination blob. + * @param sourceContainer + * A {@link String} containing the name of the source blob's container. + * @param sourceBlob + * A {@link String} containing the name of the source blob. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void copyBlob(String destinationContainer, String destinationBlob, String sourceContainer, String sourceBlob) throws ServiceException; + /** + * Copies a source blob to a destination within the storage account, using the specified options. + * <p> + * This method creates a copy of the properties, metadata, and content of the blob specified by the + * <em>sourceBlob</em> and <em>sourceContainer</em> in the destination specified by the <em>destinationBlob</em> and + * <em>destinationContainer</em> parameters. The <em>sourceContainer</em> and <em>destinationContainer</em> + * parameters may be empty to specify the root container. The destination may be a new or existing blob. Use the + * {@link CopyBlobOptions options} parameter to set an optional server timeout for the operation, an optional source + * snapshot timestamp value to copy from a particular snapshot of the source blob, new blob metadata to set on the + * destination blob, a blob lease ID to overwrite a blob with an active lease, a source lease ID to copy from a + * source blob with an active lease, any access conditions to satisfy on the destination, and any access conditions + * to satisfy on the source. + * <p> + * The source blob for a copy operation may be a block blob or a page blob, or a snapshot of either. If the + * destination blob already exists, it must be of the same blob type as the source blob. + * <p> + * Copying a source blob always copies the entire blob; copying a range of bytes or a set of blocks is not + * supported. + * <p> + * A copy blob request can take any of the following forms: + * <ul> + * <li>You can copy a source blob to a destination blob with a different name from that of the source blob. The + * destination blob can be an existing blob, or a new blob created by the copy operation.</li> + * <li>You can copy a source blob to a destination blob with the same name, effectively replacing the source blob. + * Such a copy operation removes any uncommitted blocks and overwrites the blob's metadata.</li> + * <li>You can copy a snapshot over its base blob. By promoting a snapshot to the position of the base blob, you can + * restore an earlier version of a blob.</li> + * <li>You can copy a snapshot to a destination blob with a different name. The resulting destination blob is a + * writeable blob and not a snapshot.</li> + * </ul> + * <p> + * The source blob's committed block list is also copied to the destination blob, if the blob is a block blob. Any + * uncommitted blocks are not copied. + * <p> + * The destination blob is always the same size as the source blob, so the value of the Content-Length header for + * the destination blob will be the same as that for the source blob. + * <p> + * You can specify one or more new metadata values for the destination blob by specifying the metadata in the + * <em>options</em> parameter. If this option is not specified, the metadata associated with the source blob is + * copied to the destination blob. + * <p> + * When the source blob and destination blob are the same, Copy Blob removes any uncommitted blocks. If metadata is + * specified in this case, the existing metadata is overwritten with the new metadata. + * <p> + * You can specify access conditions on the request to copy the blob only if a condition is met. If the specified + * conditions are not met, the blob is not copied, and the Blob service returns status code 412 (Precondition + * Failed), which causes a {@link ServiceException} to be thrown. + * <p> + * If the destination blob has an active lease, you must specify a valid lease ID for the active lease in order to + * copy the blob. + * <p> + * If the source blob has an active lease, you can optionally specify the lease ID for the source blob to copy the + * source blob conditionally. In this case, the source blob will be copied only if the lease ID for the source blob + * matches that specified on the request. + * <p> + * Copying a blob does not affect an existing lease on the destination blob. The destination blob's lease is + * maintained, whether you are copying a blob to a destination blob with a different name from the source, copying + * the blob to a destination blob with the same name as the source, or copying a snapshot over its base blob. + * <p> + * When a source blob is copied, any snapshots of the source blob are not copied to the destination. When a + * destination blob is overwritten with a copy, any snapshots associated with the destination blob stay intact under + * its name. + * + * @param destinationContainer + * A {@link String} containing the name of the destination blob's container. + * @param destinationBlob + * A {@link String} containing the name of the destination blob. + * @param sourceContainer + * A {@link String} containing the name of the source blob's container. + * @param sourceBlob + * A {@link String} containing the name of the source blob. + * @param options + * A {@link CopyBlobOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void copyBlob(String destinationContainer, String destinationBlob, String sourceContainer, String sourceBlob, CopyBlobOptions options) throws ServiceException; + /** + * Gets a new lease on a blob. + * <p> + * This method requests a lease on the blob specified by the <em>blob</em> and <em>container</em> parameters. If the + * blob does not have an active lease, the Blob service creates a new one-minute lock for write operations on the + * blob and returns a new lease ID. Any operation that writes to the blob's properties, metadata, or content for the + * duration of the lease must include this lease ID to succeed. You may request a new lease only for a blob without + * an unexpired lease. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to acquire a lease on. + * @return + * An {@link AcquireLeaseResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ AcquireLeaseResult acquireLease(String container, String blob) throws ServiceException; + /** + * Gets a new lease on a blob, using the specified options. + * <p> + * This method requests a lease on the blob specified by the <em>blob</em> and <em>container</em> parameters. Use + * the {@link AcquireLeaseOptions options} parameter to set an optional server timeout for the operation and any + * access conditions for the operation. If the blob does not have an active lease, the Blob service creates a new + * one-minute lock for write operations on the blob and returns a new lease ID. Any operation that writes to the + * blob's properties, metadata, or content for the duration of the lease must include this lease ID to succeed. You + * may request a new lease only for a blob without an unexpired lease. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to acquire a lease on. + * @param options + * An {@link AcquireLeaseOptions} instance containing options for the request. + * @return + * An {@link AcquireLeaseResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ AcquireLeaseResult acquireLease(String container, String blob, AcquireLeaseOptions options) throws ServiceException; + /** + * Renews a lease on a blob. + * <p> + * This method renews the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters with + * its most recent lease ID specified by the <em>leaseId</em> parameter. Note that the lease may be renewed even if + * it has expired or been released as long as the blob has not been modified since the expiration or release of that + * lease. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to renew a lease on. + * @param leaseId + * A {@link String} containing the lease ID. + * @return + * An {@link AcquireLeaseResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ AcquireLeaseResult renewLease(String container, String blob, String leaseId) throws ServiceException; + /** + * Renews a lease on a blob, using the specified options. + * <p> + * This method renews the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters with + * its most recent lease ID specified by the <em>leaseId</em> parameter. Use the {@link BlobServiceOptions options} + * parameter to specify an optional server timeout for the operation. Note that the lease may be renewed even if it + * has expired or been released as long as the blob has not been modified since the expiration or release of that + * lease. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to renew a lease on. + * @param leaseId + * A {@link String} containing the lease ID. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @return + * An {@link AcquireLeaseResult} instance containing the server response to the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ AcquireLeaseResult renewLease(String container, String blob, String leaseId, BlobServiceOptions options) throws ServiceException; + /** + * Releases an active or broken lease on a blob. + * <p> + * This method releases the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters with + * a broken or active lease ID specified by the <em>leaseId</em> parameter. Releasing the lease allows another + * client to immediately acquire the lease for the blob as soon as the release is complete. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to release a lease on. + * @param leaseId + * A {@link String} containing the lease ID. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void releaseLease(String container, String blob, String leaseId) throws ServiceException; + /** + * Releases an active or broken lease on a blob, using the specified options. + * <p> + * This method releases the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters with + * a broken or active lease ID specified by the <em>leaseId</em> parameter. Use the {@link BlobServiceOptions + * options} parameter to specify the server timeout for the operation. Releasing the lease allows another client to + * immediately acquire the lease for the blob as soon as the release is complete. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to release a lease on. + * @param leaseId + * A {@link String} containing the lease ID. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void releaseLease(String container, String blob, String leaseId, BlobServiceOptions options) throws ServiceException; + /** + * Breaks an active lease on a blob. + * <p> + * This method breaks the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters. The + * <em>leaseId</em> parameter is not used by the server. + * <p> + * Once a lease is broken, it cannot be renewed. Any authorized request can break the lease; the request is not + * required to specify a matching lease ID. When a lease is broken, the remaining time on the lease is allowed to + * elapse, during which time no lease operation may be performed on the blob. + * <p> + * A lease that has been broken but has not yet expired can also be released, in which case another client may + * immediately acquire a new lease on the blob. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to break a lease on. + * @param leaseId + * A {@link String} containing an optional lease ID. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void breakLease(String container, String blob, String leaseId) throws ServiceException; + /** + * Breaks an active lease on a blob, using the specified options. + * <p> + * This method breaks the lease on the blob specified by the <em>blob</em> and <em>container</em> parameters. The + * <em>leaseId</em> parameter is not used by the server. Use the {@link BlobServiceOptions options} parameter to + * specify the server timeout for the operation. + * <p> + * Once a lease is broken, it cannot be renewed. Any authorized request can break the lease; the request is not + * required to specify a matching lease ID. When a lease is broken, the remaining time on the lease is allowed to + * elapse, during which time no lease operation may be performed on the blob. + * <p> + * A lease that has been broken but has not yet expired can also be released, in which case another client may + * immediately acquire a new lease on the blob. + * + * @param container + * A {@link String} containing the name of the blob's container. + * @param blob + * A {@link String} containing the name of the blob to break a lease on. + * @param leaseId + * A {@link String} containing an optional lease ID. + * @param options + * A {@link BlobServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void breakLease(String container, String blob, String leaseId, BlobServiceOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobService.java index 6a35854124a0e..0e48a3033dfb5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobService.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobService.java @@ -2,36 +2,83 @@ * Copyright 2011 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 + * 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. + * 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.blob; import com.microsoft.windowsazure.services.core.Configuration; +/** + * A class for static factory methods that return instances implementing {@link BlobContract}. + */ public class BlobService { private BlobService() { } + /** + * A static factory method that returns an instance implementing {@link BlobContract} using default values for + * initializing a {@link Configuration} instance. Note that the returned interface will not work unless storage + * account credentials have been added to the "META-INF/com.microsoft.windowsazure.properties" resource file. + * + * @return + * An instance implementing {@link BlobContract} for interacting with the blob service. + */ public static BlobContract create() { return create(null, Configuration.getInstance()); } + /** + * A static factory method that returns an instance implementing {@link BlobContract} using the specified + * {@link Configuration} instance. The {@link Configuration} instance must have storage account information and + * credentials set before this method is called for the returned interface to work. + * + * @param config + * A {@link Configuration} instance configured with storage account information and credentials. + * + * @return + * An instance implementing {@link BlobContract} for interacting with the blob service. + */ public static BlobContract create(Configuration config) { return create(null, config); } + /** + * A static factory method that returns an instance implementing {@link BlobContract} using default values for + * initializing a {@link Configuration} instance, and using the specified profile prefix for service settings. Note + * that the returned interface will not work unless storage account settings and credentials have been added to the + * "META-INF/com.microsoft.windowsazure.properties" resource file with the specified profile prefix. + * + * @param profile + * A string prefix for the account name and credentials settings in the {@link Configuration} instance. + * @return + * An instance implementing {@link BlobContract} for interacting with the blob service. + */ public static BlobContract create(String profile) { return create(profile, Configuration.getInstance()); } + /** + * A static factory method that returns an instance implementing {@link BlobContract} using the specified + * {@link Configuration} instance and profile prefix for service settings. The {@link Configuration} instance must + * have storage account information and credentials set with the specified profile prefix before this method is + * called for the returned interface to work. + * + * @param profile + * A string prefix for the account name and credentials settings in the {@link Configuration} instance. + * @param config + * A {@link Configuration} instance configured with storage account information and credentials. + * + * @return + * An instance implementing {@link BlobContract} for interacting with the blob service. + */ public static BlobContract create(String profile, Configuration config) { return config.create(profile, BlobContract.class); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseOptions.java index af9ea1931ee2c..fe6e69ed87132 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseOptions.java @@ -2,32 +2,68 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; +/** + * Represents the options that may be set on an {@link BlobContract#acquireLease(String, String, AcquireLeaseOptions) + * acquireLease} request. + * These options include an optional server timeout for the operation and any access conditions for the operation. + */ public class AcquireLeaseOptions extends BlobServiceOptions { private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link AcquireLeaseOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link AcquireLeaseOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link AcquireLeaseOptions} instance. + */ @Override public AcquireLeaseOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the access conditions set in this {@link AcquireLeaseOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for acquiring a lease on a blob. By default, the operation will acquire the lease + * unconditionally. Use this method to specify conditions on the ETag or last modified time value for performing the + * operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this {@link AcquireLeaseOptions} + * instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link AcquireLeaseOptions} instance. + */ public AcquireLeaseOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseResult.java index 1565c42585004..785d10473f081 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AcquireLeaseResult.java @@ -2,25 +2,55 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Lease Blob operation. This is returned by + * calls to implementations of {@link BlobContract#breakLease(String, String, String, BlobServiceOptions)}, + * {@link BlobContract#releaseLease(String, String, String, BlobServiceOptions)} and + * {@link BlobContract#renewLease(String, String, String, BlobServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691972.aspx">Lease Blob</a> documentation on + * MSDN for details of the underlying Blob Service REST API operation. + */ public class AcquireLeaseResult { private String leaseId; + /** + * Gets the lease ID of the blob. + * <p> + * This value is used when updating or deleting a blob with an active lease, and when renewing or releasing the + * lease. + * + * @return + * A {@link String} containing the server-assigned lease ID for the blob. + */ public String getLeaseId() { return leaseId; } + /** + * Reserved for internal use. Sets the lease ID of the blob from the <strong>x-ms-lease-id</strong> header of the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param leaseId + * A {@link String} containing the server-assigned lease ID for the blob. + */ public void setLeaseId(String leaseId) { this.leaseId = leaseId; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobProperties.java index 73d968d1153f6..f5231d28254a2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobProperties.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -21,6 +21,9 @@ import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateAdapter; +/** + * Represents the HTML properties and system properties that may be set on a blob. + */ public class BlobProperties { private Date lastModified; private String etag; @@ -34,102 +37,263 @@ public class BlobProperties { private String leaseStatus; private long sequenceNumber; + /** + * Gets the last modified time of the blob. For block blobs, this value is returned only if the blob has committed + * blocks. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the blob. + */ @XmlElement(name = "Last-Modified") @XmlJavaTypeAdapter(RFC1123DateAdapter.class) public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <code>Last-Modified</code> header + * value returned in a server response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the ETag of the blob. For block blobs, this value is returned only if the blob has committed blocks. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ @XmlElement(name = "Etag") public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag value of the blob from the <code>ETag</code> header value returned in a + * server response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the MIME content type of the blob. + * + * @return + * A {@link String} containing the MIME content type value for the blob. + */ @XmlElement(name = "Content-Type") public String getContentType() { return contentType; } + /** + * Reserved for internal use. Sets the MIME content type value for the blob from the <code>Content-Type</code> + * header value returned in the server response. + * + * @param contentType + * A {@link String} containing the MIME content type value for the blob. + */ public void setContentType(String contentType) { this.contentType = contentType; } + /** + * Gets the size of the blob in bytes. + * + * @return + * The size of the blob in bytes. + */ @XmlElement(name = "Content-Length") public long getContentLength() { return contentLength; } + /** + * Reserved for internal use. Sets the content length value for the blob from the <code>Content-Length</code> header + * value returned in the server response. + * + * @param contentLength + * The size of the blob in bytes. + */ public void setContentLength(long contentLength) { this.contentLength = contentLength; } + /** + * Gets the HTTP content encoding value of the blob. + * + * @return + * A {@link String} containing the HTTP content encoding value set, if any. + */ @XmlElement(name = "Content-Encoding") public String getContentEncoding() { return contentEncoding; } + /** + * Reserved for internal use. Sets the HTTP content encoding value for the blob from the + * <code>Content-Encoding</code> header value returned in the server response. + * + * @param contentEncoding + * A {@link String} containing the HTTP content encoding value to set. + */ public void setContentEncoding(String contentEncoding) { this.contentEncoding = contentEncoding; } + /** + * Gets the HTTP content language value of the blob. + * + * @return + * A {@link String} containing the HTTP content language value set, if any. + */ @XmlElement(name = "Content-Language") public String getContentLanguage() { return contentLanguage; } + /** + * Reserved for internal use. Sets the HTTP content language value for the blob from the + * <code>Content-Language</code> header value returned in the server response. + * + * @param contentLanguage + * A {@link String} containing the HTTP content language value to set. + */ public void setContentLanguage(String contentLanguage) { this.contentLanguage = contentLanguage; } + /** + * Gets the MD5 hash value of the blob content. + * + * @return + * A {@link String} containing the MD5 hash value of the blob content. + */ @XmlElement(name = "Content-MD5") public String getContentMD5() { return contentMD5; } + /** + * Reserved for internal use. Sets the MD5 hash value of the blob content from the <code>Content-MD5</code> header + * value returned in the server response. + * + * @param contentMD5 + * A {@link String} containing the MD5 hash value of the blob content. + */ public void setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; } + /** + * Gets the HTTP cache control value of the blob. + * + * @return + * A {@link String} containing the HTTP cache control value of the blob. + */ @XmlElement(name = "Cache-Control") public String getCacheControl() { return cacheControl; } + /** + * Reserved for internal use. Sets the HTTP cache control value of the blob from the <code>Cache-Control</code> + * header value returned in the server response. + * + * @param cacheControl + * A {@link String} containing the HTTP cache control value of the blob. + */ public void setCacheControl(String cacheControl) { this.cacheControl = cacheControl; } + /** + * Gets a string representing the type of the blob, with a value of "BlockBlob" for block blobs, and "PageBlob" + * for page blobs. + * + * @return + * A {@link String} containing "BlockBlob" for block blobs, or "PageBlob" for page blobs. + */ @XmlElement(name = "BlobType") public String getBlobType() { return blobType; } + /** + * Reserved for internal use. Sets the blob type from the <code>x-ms-blob-type</code> header value returned in the + * server response. + * + * @param blobType + * A {@link String} containing "BlockBlob" for block blobs, or "PageBlob" for page blobs. + */ public void setBlobType(String blobType) { this.blobType = blobType; } + /** + * Gets a string representing the lease status of the blob, with a value of "locked" for blobs with an active lease, + * and "unlocked" for blobs without an active lease. + * + * @return + * A {@link String} containing "locked" for blobs with an active lease, and "unlocked" for blobs without an + * active lease. + */ @XmlElement(name = "LeaseStatus") public String getLeaseStatus() { return leaseStatus; } + /** + * Reserved for internal use. Sets the blob lease status from the <code>x-ms-lease-status</code> header value + * returned in the server response. + * + * @param leaseStatus + * A {@link String} containing "locked" for blobs with an active lease, and "unlocked" for blobs without + * an active lease. + */ public void setLeaseStatus(String leaseStatus) { this.leaseStatus = leaseStatus; } + /** + * Gets the current sequence number for a page blob. This value is not set for block blobs. + * + * @return + * The current sequence number of the page blob. + */ @XmlElement(name = "x-ms-blob-sequence-number") public long getSequenceNumber() { return sequenceNumber; } + /** + * Reserved for internal use. Sets the page blob sequence number from the <code>x-ms-blob-sequence-number</code> + * header value returned in the server response. + * + * @param sequenceNumber + * The current sequence number of the page blob. + */ public void setSequenceNumber(long sequenceNumber) { this.sequenceNumber = sequenceNumber; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobServiceOptions.java index 879986c2640c5..3fc56c6d1ab60 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobServiceOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlobServiceOptions.java @@ -2,26 +2,52 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the base class for options that may be set on Blob Service REST API operations invoked through the + * {@link BlobContract} interface. This class defines a server request timeout, which can be applied to all operations. + */ public class BlobServiceOptions { // Nullable because it is optional private Integer timeout; + /** + * Gets the server request timeout value associated with this {@link BlobServiceOptions} instance. + * <p> + * The timeout value only affects calls made on methods where this {@link BlobServiceOptions} instance is passed as + * a parameter. + * + * @return + * The server request timeout value in milliseconds. + */ public Integer getTimeout() { return timeout; } + /** + * Sets the server request timeout value associated with this {@link BlobServiceOptions} instance. + * <p> + * The timeout value only affects calls made on methods where this {@link BlobServiceOptions} instance is passed as + * a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link BlobServiceOptions} instance. + */ public BlobServiceOptions setTimeout(Integer timeout) { this.timeout = timeout; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlockList.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlockList.java index 7fcdfe314651b..320066d06f80f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlockList.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/BlockList.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -26,10 +26,21 @@ import com.microsoft.windowsazure.services.core.utils.pipeline.Base64StringAdapter; +/** + * Represents a list of blocks that may be committed to a block blob. + */ @XmlRootElement(name = "BlockList") public class BlockList { private List<Entry> entries = new ArrayList<Entry>(); + /** + * Adds the committed block specified by the block ID to the block list. + * + * @param blockId + * A {@link String} containing the client-specified block ID for a committed block. + * @return + * A reference to this {@link BlockList} instance. + */ public BlockList addCommittedEntry(String blockId) { CommittedEntry entry = new CommittedEntry(); entry.setBlockId(blockId); @@ -37,6 +48,14 @@ public BlockList addCommittedEntry(String blockId) { return this; } + /** + * Adds the uncommitted block specified by the block ID to the block list. + * + * @param blockId + * A {@link String} containing the client-specified block ID for an uncommitted block. + * @return + * A reference to this {@link BlockList} instance. + */ public BlockList addUncommittedEntry(String blockId) { UncommittedEntry entry = new UncommittedEntry(); entry.setBlockId(blockId); @@ -44,6 +63,16 @@ public BlockList addUncommittedEntry(String blockId) { return this; } + /** + * Adds the latest block specified by the block ID to the block list. An entry of this type will cause the server + * commit the most recent uncommitted block with the specified block ID, or the committed block with the specified + * block ID if no uncommitted block is found. + * + * @param blockId + * A {@link String} containing the client-specified block ID for the latest matching block. + * @return + * A reference to this {@link BlockList} instance. + */ public BlockList addLatestEntry(String blockId) { LatestEntry entry = new LatestEntry(); entry.setBlockId(blockId); @@ -51,6 +80,12 @@ public BlockList addLatestEntry(String blockId) { return this; } + /** + * Gets the collection of entries for the block list. + * + * @return + * A {@link List} of {@link Entry} instances specifying the blocks to commit. + */ @XmlElementRefs({ @XmlElementRef(name = "Committed", type = CommittedEntry.class), @XmlElementRef(name = "Uncommitted", type = UncommittedEntry.class), @XmlElementRef(name = "Latest", type = LatestEntry.class) }) @@ -59,33 +94,66 @@ public List<Entry> getEntries() { return entries; } + /** + * Sets the block list to the specified collection of entries. + * + * @param entries + * A {@link List} of {@link Entry} instances specifying the blocks to commit. + * @return + * A reference to this {@link BlockList} instance. + */ public BlockList setEntries(List<Entry> entries) { this.entries = entries; return this; } + /** + * The abstract base class for an entry in a {@link BlockList}, representing a committed or uncommitted block. + */ public abstract static class Entry { private String blockId; + /** + * Gets the client-specified block ID for a {@link BlockList} entry. + * + * @return + * A {@link String} containing the client-specified block ID for a block. + */ @XmlJavaTypeAdapter(Base64StringAdapter.class) @XmlValue public String getBlockId() { return blockId; } + /** + * Sets the client-specified block ID for a {@link BlockList} entry. + * + * @param blockId + * A {@link String} containing the client-specified block ID for the block. + */ public void setBlockId(String blockId) { this.blockId = blockId; } } + /** + * Represents an entry in a {@link BlockList} for a previously committed block. + */ @XmlRootElement(name = "Committed") public static class CommittedEntry extends Entry { } + /** + * Represents an entry in a {@link BlockList} for an uncommitted block. + */ @XmlRootElement(name = "Uncommitted") public static class UncommittedEntry extends Entry { } + /** + * Represents an entry in a {@link BlockList} for the most recent uncommitted block with the specified block ID, or + * the committed block with the specified block ID if no uncommitted block is found. + */ @XmlRootElement(name = "Latest") public static class LatestEntry extends Entry { } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CommitBlobBlocksOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CommitBlobBlocksOptions.java index b502f18929df1..9aa632f80f60d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CommitBlobBlocksOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CommitBlobBlocksOptions.java @@ -2,20 +2,29 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#commitBlobBlocks(String, String, BlockList, CommitBlobBlocksOptions) commitBlobBlocks} request. + * These options include an optional server timeout for the operation, the MIME content type and content encoding for + * the blob, the content language, the MD5 hash, a cache control value, blob metadata, a blob lease ID, and any access + * conditions for the operation. + */ public class CommitBlobBlocksOptions extends BlobServiceOptions { private String blobContentType; private String blobContentEncoding; @@ -26,84 +35,264 @@ public class CommitBlobBlocksOptions extends BlobServiceOptions { private String leaseId; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link CommitBlobBlocksOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ @Override public CommitBlobBlocksOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the MIME content type value set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the MIME content type value set, if any. + */ public String getBlobContentType() { return blobContentType; } + /** + * Sets the optional MIME content type for the blob content. This value will be returned to clients in the + * <code>Content-Type</code> header of the response when the blob data or blob properties are requested. If no + * content type is specified, the default content type is <strong>application/octet-stream</strong>. + * <p> + * Note that this value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param blobContentType + * A {@link String} containing the MIME content type value to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setBlobContentType(String blobContentType) { this.blobContentType = blobContentType; return this; } + /** + * Gets the HTTP content encoding value set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the HTTP content encoding value set, if any. + */ public String getBlobContentEncoding() { return blobContentEncoding; } + /** + * Sets the optional HTTP content encoding value for the blob content. Use this value to specify any HTTP content + * encodings applied to the blob, passed as a <code>x-ms-blob-content-encoding</code> header value to the server. + * This value will be returned to clients in the headers of the response when the blob data or blob properties are + * requested. Pass an empty value to update a blob to the default value, which will cause no content encoding header + * to be returned with the blob. + * <p> + * Note that this value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param blobContentEncoding + * A {@link String} containing the <code>x-ms-blob-content-encoding</code> header value to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setBlobContentEncoding(String blobContentEncoding) { this.blobContentEncoding = blobContentEncoding; return this; } + /** + * Gets the HTTP content language header value set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the HTTP content language header value set, if any. + */ public String getBlobContentLanguage() { return blobContentLanguage; } + /** + * Sets the optional HTTP content language header value for the blob content. Use this value to + * specify the content language of the blob. This value will be returned to clients in the + * <code>x-ms-blob-content-language</code> header of the response when the blob data or blob properties are + * requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param blobContentLanguage + * A {@link String} containing the <code>x-ms-blob-content-language</code> header value to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setBlobContentLanguage(String blobContentLanguage) { this.blobContentLanguage = blobContentLanguage; return this; } + /** + * Gets the MD5 hash value for the blob content set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the MD5 hash value for the blob content set, if any. + */ public String getBlobContentMD5() { return blobContentMD5; } + /** + * Sets the optional MD5 hash value for the blob content. This value will be returned to clients in the + * <code>x-ms-blob-content-md5</code> header value of the response when the blob data or blob properties are + * requested. This hash is used to verify the integrity of the blob during transport. When this header is specified, + * the storage service checks the hash of the content that has arrived with the one that was sent. If the two hashes + * do not match, the operation will fail with error code 400 (Bad Request), which will cause a ServiceException to + * be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param blobContentMD5 + * A {@link String} containing the MD5 hash value for the blob content to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setBlobContentMD5(String blobContentMD5) { this.blobContentMD5 = blobContentMD5; return this; } + /** + * Gets the HTTP cache control value set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the HTTP cache control value set, if any. + */ public String getBlobCacheControl() { return blobCacheControl; } + /** + * Sets the optional HTTP cache control value for the blob content. The Blob service stores + * this value but does not use or modify it. This value will be returned to clients in the headers of the response + * when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param blobCacheControl + * A {@link String} containing the HTTP cache control value to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setBlobCacheControl(String blobCacheControl) { this.blobCacheControl = blobCacheControl; return this; } + /** + * Gets the blob metadata collection set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata set, if any. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Sets the blob metadata collection to associate with the created blob. Metadata is a collection of name-value + * {@link String} pairs for client use and is opaque to the server. Metadata names must adhere to the naming rules + * for <a href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * <p> + * The <em>metadata</em> value only affects calls made on methods where this {@link CommitBlobBlocksOptions} + * instance is passed as a parameter. + * + * @param metadata + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; return this; } + /** + * Adds a name-value pair to the blob metadata collection associated with this {@link CommitBlobBlocksOptions} + * instance. + * + * @param key + * A {@link String} containing the name portion of the name-value pair to add to the metadata collection. + * @param value + * A {@link String} containing the value portion of the name-value pair to add to the metadata + * collection. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions addMetadata(String key, String value) { this.getMetadata().put(key, value); return this; } + /** + * Gets the lease ID for the blob set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when updating the blob. If the blob has a lease, this parameter must be set with + * the matching leaseId value for the commit block blobs operation to succeed. + * <p> + * The <em>leaseId</em> value only affects calls made on methods where this {@link CommitBlobBlocksOptions} instance + * is passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the access conditions set in this {@link CommitBlobBlocksOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for updating a blob. By default, the commit block blobs operation will set the + * container metadata unconditionally. Use this method to specify conditions on the ETag or last modified time value + * for performing the commit block blobs operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this {@link CommitBlobBlocksOptions} + * instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link CommitBlobBlocksOptions} instance. + */ public CommitBlobBlocksOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ContainerACL.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ContainerACL.java index 628d08d95cab6..d5f6005b41ea2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ContainerACL.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ContainerACL.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -22,46 +22,174 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import com.microsoft.windowsazure.services.blob.BlobContract; import com.microsoft.windowsazure.services.blob.implementation.ContainerACLDateAdapter; +/** + * Represents the public access properties and the container-level access policies of a container in the Blob storage + * service. This is returned by calls to implementations of {@link BlobContract#getContainerACL(String)} and + * {@link BlobContract#getContainerACL(String, BlobServiceOptions)}, and passed as a parameter to calls to + * implementations of {@link BlobContract#setContainerACL(String, ContainerACL)} and + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179469.aspx">Get Container ACL</a> and the <a + * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179391.aspx">Set Container ACL</a> documentation on MSDN + * for details of the underlying Blob Service REST API operations. + */ public class ContainerACL { private String etag; private Date lastModified; private PublicAccessType publicAccess; private List<SignedIdentifier> signedIdentifiers = new ArrayList<SignedIdentifier>(); + /** + * Gets the <strong>Etag</strong> value associated with this {@link ContainerACL} instance. This is the value + * returned for a container by a Blob service REST API Get Container ACL operation, or the value to set on a + * container with a Set Container ACL operation. + * + * @return + * A {@link String} containing the <strong>Etag</strong> value associated with this {@link ContainerACL} + * instance. + */ public String getEtag() { return etag; } + /** + * Sets the <strong>Etag</strong> value to associate with this {@link ContainerACL} instance. + * <p> + * This value is only set on a container when this {@link ContainerACL} instance is passed as a parameter to a call + * to an implementation of {@link BlobContract#setContainerACL(String, ContainerACL)} or + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * + * @param etag + * A {@link String} containing the <strong>Etag</strong> value to associate with this + * {@link ContainerACL} instance. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time associated with this {@link ContainerACL} instance. This is the value + * returned for a container by a Blob service REST API Get Container ACL operation, or the value to set on a + * container with a Set Container ACL operation. + * + * @return + * A {@link Date} containing the last modified time associated with this {@link ContainerACL} instance. + */ public Date getLastModified() { return lastModified; } + /** + * Sets the last modified time to associate with this {@link ContainerACL} instance. + * <p> + * This value is only set on a container when this {@link ContainerACL} instance is passed as a parameter to a call + * to an implementation of {@link BlobContract#setContainerACL(String, ContainerACL)} or + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * + * @param lastModified + * A {@link Date} containing the last modified time to associate with this {@link ContainerACL} instance. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the public access level associated with this {@link ContainerACL} instance. This is the value + * returned for a container by a Blob service REST API Get Container ACL operation, or the value to set on a + * container with a Set Container ACL operation. + * + * @return + * A {@link PublicAccessType} value representing the public access level associated with this + * {@link ContainerACL} instance. + */ public PublicAccessType getPublicAccess() { return publicAccess; } + /** + * Sets the public access level to associate with this {@link ContainerACL} instance. + * <p> + * This value is only set on a container when this {@link ContainerACL} instance is passed as a parameter to a call + * to an implementation of {@link BlobContract#setContainerACL(String, ContainerACL)} or + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * + * @param publicAccess + * A {@link PublicAccessType} value representing the public access level to associate with this + * {@link ContainerACL} instance. + */ public void setPublicAccess(PublicAccessType publicAccess) { this.publicAccess = publicAccess; } + /** + * Gets the list of container-level access policies associated with this {@link ContainerACL} instance. This is the + * value returned for a container by a Blob service REST API Get Container ACL operation, or the value to set on a + * container with a Set Container ACL operation. + * + * @return + * A {@link List} of {@link SignedIdentifier} instances containing up to five container-level access + * policies associated with this {@link ContainerACL} instance. + */ public List<SignedIdentifier> getSignedIdentifiers() { return signedIdentifiers; } + /** + * Sets the list of container-level access policies to associate with this {@link ContainerACL} instance. + * <p> + * This value is only set on a container when this {@link ContainerACL} instance is passed as a parameter to a call + * to an implementation of {@link BlobContract#setContainerACL(String, ContainerACL)} or + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * + * @param signedIdentifiers + * A {@link List} of {@link SignedIdentifier} instances containing up to five container-level access + * policies to associate with this {@link ContainerACL} instance. + */ public void setSignedIdentifiers(List<SignedIdentifier> signedIdentifiers) { this.signedIdentifiers = signedIdentifiers; } + /** + * Adds a container-level access policy to the list associated with this {@link ContainerACL} instance. A container + * may have up to five container-level access policies. + * <p> + * Use the <em>id</em> parameter to specify a name for the access policy. The name may be up to 64 characters in + * length and must be unique within the container. + * <p> + * Use the <em>start</em> parameter to specify the start time for valid access to a resource using the access + * policy. If this value is <code>null</code>, the start time for any resource request using the access policy is + * assumed to be the time when the Blob service receives the request. + * <p> + * Use the <em>expiry</em> parameter to specify the expiration time for valid access to a resource using the access + * policy. If this value is <code>null</code>, the expiry time must be included in the Shared Access Signature for + * any resource request using the access policy. + * <p> + * Use the <em>permission</em> parameter to specify the operations that can be performed on a blob that is accessed + * with the access policy. Supported permissions include read (r), write (w), delete (d), and list (l). Permissions + * may be grouped so as to allow multiple operations to be performed with the access policy. For example, to grant + * all permissions to a resource, specify "rwdl" for the parameter. To grant only read/write permissions, specify + * "rw" for the parameter. + * <p> + * This value is only set on a container when this {@link ContainerACL} instance is passed as a parameter to a call + * to an implementation of {@link BlobContract#setContainerACL(String, ContainerACL)} or + * {@link BlobContract#setContainerACL(String, ContainerACL, BlobServiceOptions)}. + * + * @param id + * A {@link String} containing the name for the access policy. + * @param start + * A {@link Date} representing the start time for the access policy. If this value is <code>null</code>, + * any Shared Access Signature that refers to this policy may specify the start time. + * @param expiry + * A {@link Date} representing the expiration time for the access policy. If this value is + * <code>null</code>, any Shared Access Signature that refers to this policy must specify the expiry + * value. Resource access using a Shared Access Signature that refers to this policy after this time is + * not valid. + * @param permission + * A {@link String} containing the permissions specified for the access policy. + */ public void addSignedIdentifier(String id, Date start, Date expiry, String permission) { AccessPolicy accessPolicy = new AccessPolicy(); accessPolicy.setStart(start); @@ -75,79 +203,202 @@ public void addSignedIdentifier(String id, Date start, Date expiry, String permi this.getSignedIdentifiers().add(signedIdentifier); } + /** + * A static inner class representing a collection of container-level access policies. A container may have up to + * five container-level access policies, which may be associated with any number of Shared Access Signatures. + */ @XmlRootElement(name = "SignedIdentifiers") public static class SignedIdentifiers { private List<SignedIdentifier> signedIdentifiers = new ArrayList<SignedIdentifier>(); + /** + * Gets the list of container-level access policies associated with this {@link SignedIdentifiers} instance. + * + * @return + * A {@link List} of {@link SignedIdentifier} instances containing container-level access policies. + */ @XmlElement(name = "SignedIdentifier") public List<SignedIdentifier> getSignedIdentifiers() { return signedIdentifiers; } + /** + * Sets the list of container-level access policies associated with this {@link SignedIdentifiers} instance. + * + * @param signedIdentifiers + * A {@link List} of {@link SignedIdentifier} instances containing container-level access policies. + */ public void setSignedIdentifiers(List<SignedIdentifier> signedIdentifiers) { this.signedIdentifiers = signedIdentifiers; } } + /** + * A static inner class representing a container-level access policy with a unique name. + */ public static class SignedIdentifier { private String id; private AccessPolicy accessPolicy; + /** + * Gets the name of the container-level access policy. The name may be up to 64 characters in + * length and must be unique within the container. + * + * @return + * A {@link String} containing the name for the access policy. + */ @XmlElement(name = "Id") public String getId() { return id; } + /** + * Sets the name of the container-level access policy. The name may be up to 64 characters in + * length and must be unique within the container. + * + * @param id + * A {@link String} containing the name for the access policy. + */ public void setId(String id) { this.id = id; } + /** + * Gets an {@link AccessPolicy} reference containing the start time, expiration time, and permissions associated + * with the container-level access policy. + * + * @return + * An {@link AccessPolicy} reference containing the start time, expiration time, and permissions + * associated with the access policy. + */ @XmlElement(name = "AccessPolicy") public AccessPolicy getAccessPolicy() { return accessPolicy; } + /** + * Sets an {@link AccessPolicy} reference containing the start time, expiration time, and permissions to + * associate with the container-level access policy. + * + * @param accessPolicy + * An {@link AccessPolicy} reference containing the start time, expiration time, and permissions + * to associate with the access policy. + */ public void setAccessPolicy(AccessPolicy accessPolicy) { this.accessPolicy = accessPolicy; } } + /** + * An inner class representing the start time, expiration time, and permissions associated with an access policy. + */ public static class AccessPolicy { private Date start; private Date expiry; private String permission; + /** + * Gets the start time for valid access to a resource using the access policy. If this value is + * <code>null</code>, the start time for any resource request using the access policy is assumed to be the time + * when the Blob service receives the request. + * + * @return + * A {@link Date} representing the start time for the access policy, or <code>null</code> if none is + * specified. + */ @XmlElement(name = "Start") @XmlJavaTypeAdapter(ContainerACLDateAdapter.class) public Date getStart() { return start; } + /** + * Sets the start time for valid access to a resource using the access policy. If this value is + * <code>null</code>, the start time for any resource request using the access policy is assumed to be the time + * when the Blob service receives the request. + * + * @param start + * A {@link Date} representing the start time for the access policy, or <code>null</code> to leave + * the time unspecified. + */ public void setStart(Date start) { this.start = start; } + /** + * Gets the expiration time for valid access to a resource using the access policy. If this value is + * <code>null</code>, any Shared Access Signature that refers to this access policy must specify the expiry + * value. + * + * @return + * A {@link Date} representing the expiration time for the access policy, or <code>null</code> if none + * is specified. + */ @XmlElement(name = "Expiry") @XmlJavaTypeAdapter(ContainerACLDateAdapter.class) public Date getExpiry() { return expiry; } + /** + * Sets the expiration time for valid access to a resource using the access policy. If this value is + * <code>null</code>, any Shared Access Signature that refers to this access policy must specify the expiry + * value. + * + * @param expiry + * A {@link Date} representing the expiration time for the access policy, or <code>null</code> to + * leave the time unspecified. + */ public void setExpiry(Date expiry) { this.expiry = expiry; } + /** + * Gets the permissions for operations on resources specified by the access policy. Supported permissions + * include read (r), write (w), delete (d), and list (l). Permissions may be grouped so as to allow multiple + * operations to be performed with the access policy. For example, if all permissions to a resource are granted, + * the method returns "rwdl" as the result. If only read/write permissions are granted, the method returns "rw" + * as the result. + * + * @return + * A {@link String} containing the permissions specified for the access policy. + */ @XmlElement(name = "Permission") public String getPermission() { return permission; } + /** + * Sets the permissions for operations on resources specified by the access policy. Supported permissions + * include read (r), write (w), delete (d), and list (l). Permissions may be grouped so as to allow multiple + * operations to be performed with the access policy. For example, to grant all permissions to a resource, + * specify "rwdl" for the parameter. To grant only read/write permissions, specify "rw" for the parameter. + * + * @param permission + * A {@link String} containing the permissions specified for the access policy. + */ public void setPermission(String permission) { this.permission = permission; } } + /** + * An enumeration type for the public access levels that can be set on a blob container. + */ public static enum PublicAccessType { - NONE, BLOBS_ONLY, CONTAINER_AND_BLOBS, + /** + * Access to this container and its blobs is restricted to calls made with the storage account private key. + */ + NONE, + /** + * Anonymous public read-only access is allowed for individual blobs within the container, but it is not + * possible to enumerate the blobs within the container or to enumerate the containers in the storage account. + */ + BLOBS_ONLY, + /** + * Anonymous public read-only access is allowed for individual blobs within the container, and the blobs within + * the container can be enumerated, but it is not possible to enumerate the containers in the storage account. + */ + CONTAINER_AND_BLOBS, } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CopyBlobOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CopyBlobOptions.java index dcecf99c157f3..484829480329d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CopyBlobOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CopyBlobOptions.java @@ -2,20 +2,30 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#copyBlob(String, String, String, String, CopyBlobOptions) copyBlob} request. These options + * include an optional server timeout for the operation, an optional source snapshot timestamp value to copy from a + * particular snapshot of the source blob, blob metadata to set on the destination blob, a blob lease ID + * to overwrite a blob with an active lease, a source lease ID to copy from a source blob with an active lease, any + * access conditions to satisfy on the destination, and any access conditions to satisfy on the source. + */ public class CopyBlobOptions extends BlobServiceOptions { private String leaseId; private AccessCondition accessCondition; @@ -24,66 +34,204 @@ public class CopyBlobOptions extends BlobServiceOptions { private HashMap<String, String> metadata = new HashMap<String, String>(); private AccessCondition sourceAccessCondition; + /** + * Sets the optional server request timeout value associated with this {@link CopyBlobOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CopyBlobOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ @Override public CopyBlobOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the source snapshot timestamp value set in this {@link CopyBlobOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the source blob snapshot to list. + */ public String getSourceSnapshot() { return sourceSnapshot; } + /** + * Sets the snapshot timestamp value used to identify the particular snapshot of the source blob to copy. The + * snapshot timestamp value is an opaque value returned by the server to identify a snapshot. When this option is + * set, the properties, metadata, and content of the snapshot are copied to the destination. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param sourceSnapshot + * A {@link String} containing the snapshot timestamp value of the source blob snapshot to list. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ public CopyBlobOptions setSourceSnapshot(String sourceSnapshot) { this.sourceSnapshot = sourceSnapshot; return this; } + /** + * Gets the blob metadata collection set in this {@link CopyBlobOptions} instance. + * + * @return + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata set, if any. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Sets the blob metadata collection to associate with the destination blob. Metadata is a collection of name-value + * {@link String} pairs for client use and is opaque to the server. Metadata names must adhere to the naming rules + * for <a href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * <p> + * Note that if any metadata is set with this option, no source blob metadata will be copied to the destination + * blob. + * <p> + * The <em>metadata</em> value only affects calls made on methods where this {@link CreateBlobOptions} instance is + * passed as a parameter. + * + * @param metadata + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CopyBlobOptions setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; return this; } + /** + * Adds a name-value pair to the blob metadata collection associated with this {@link CopyBlobOptions} instance. + * <p> + * Note that if any metadata is set with this option, no source blob metadata will be copied to the destination + * blob. + * + * @param key + * A {@link String} containing the name portion of the name-value pair to add to the metadata collection. + * @param value + * A {@link String} containing the value portion of the name-value pair to add to the metadata + * collection. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ public CopyBlobOptions addMetadata(String key, String value) { this.getMetadata().put(key, value); return this; } + /** + * Gets the lease ID to match for the destination blob set in this {@link CopyBlobOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match on the destination blob. If set, there must be an active lease with a matching + * lease ID set on the destination blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link CopyBlobOptions} instance is passed as + * a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ public CopyBlobOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the lease ID to match for the source blob set in this {@link CopyBlobOptions} instance. + * + * @return + * A {@link String} containing the source blob lease ID set, if any. + */ public String getSourceLeaseId() { return sourceLeaseId; } + /** + * Sets a lease ID value to match on the source blob. If set, there must be an active lease with a matching + * lease ID set on the source blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link CopyBlobOptions} instance is passed as + * a parameter. + * + * @param sourceLeaseId + * A {@link String} containing the source blob lease ID to set. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ public CopyBlobOptions setSourceLeaseId(String sourceLeaseId) { this.sourceLeaseId = sourceLeaseId; return this; } + /** + * Gets the access conditions on the destination blob set in this {@link CopyBlobOptions} instance. + * + * @return + * An {@link AccessCondition} containing the destination blob access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for the destination blob. The operation will return an error if the access + * conditions are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link CopyBlobOptions} instance is passed as + * a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the destination blob access conditions to set. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ public CopyBlobOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; } + /** + * Gets the access conditions on the source blob set in this {@link CopyBlobOptions} instance. + * + * @return + * An {@link AccessCondition} containing the source blob access conditions set, if any. + */ public AccessCondition getSourceAccessCondition() { return sourceAccessCondition; } + /** + * Sets optional access conditions for the source blob. The operation will return an error if the access + * conditions are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link CopyBlobOptions} instance is passed as + * a parameter. + * + * @param sourceAccessCondition + * An {@link AccessCondition} containing the source blob access conditions to set. + * @return + * A reference to this {@link CopyBlobOptions} instance. + */ public CopyBlobOptions setSourceAccessCondition(AccessCondition sourceAccessCondition) { this.sourceAccessCondition = sourceAccessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobBlockOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobBlockOptions.java index 65b3d4b40855f..98bdb31d5d99a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobBlockOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobBlockOptions.java @@ -2,41 +2,98 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#createBlobBlock(String, String, String, java.io.InputStream, CreateBlobBlockOptions) + * createBlobBlock} request. These options include an optional server timeout for the operation, the lease ID if the + * blob has an active lease, and the MD5 hash value for the block content. + */ public class CreateBlobBlockOptions extends BlobServiceOptions { private String leaseId; private String contentMD5; + /** + * Sets the optional server request timeout value associated with this {@link CreateBlobBlockOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CreateBlobBlockOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CreateBlobBlockOptions} instance. + */ @Override public CreateBlobBlockOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the lease ID to match for the blob set in this {@link CreateBlobBlockOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when updating the blob. This value must match the lease ID set on a leased blob + * for an update to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobBlockOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CreateBlobBlockOptions} instance. + */ public CreateBlobBlockOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the MD5 hash value for the block content set in this {@link CreateBlobBlockOptions} instance. + * + * @return + * A {@link String} containing the MD5 hash value for the block content set, if any. + */ public String getContentMD5() { return contentMD5; } + /** + * Sets the optional MD5 hash value for the block content. This hash is used to verify the integrity of the blob + * during transport. When this value is specified, the storage service checks the hash of the content that has + * arrived with the one that was sent. If the two hashes do not match, the operation will fail with error code 400 + * (Bad Request), which will cause a ServiceException to be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobBlockOptions} instance is + * passed as a parameter. + * + * @param contentMD5 + * A {@link String} containing the MD5 hash value for the block content to set. + * @return + * A reference to this {@link CreateBlobBlockOptions} instance. + */ public CreateBlobBlockOptions setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobOptions.java index 41fef87ff0cbf..e3afa134df8a2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobOptions.java @@ -2,20 +2,30 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#createPageBlob(String, String, int, CreateBlobOptions) createPageBlob} or + * {@link BlobContract#createBlockBlob(String, String, java.io.InputStream, CreateBlobOptions) createBlockBlob} request. + * These options include an optional server timeout for the operation, the MIME content type and content encoding for + * the blob, the content language, the MD5 hash, a cache control value, blob metadata, a blob lease ID, a sequence + * number, and access conditions. + */ public class CreateBlobOptions extends BlobServiceOptions { private String contentType; private String contentEncoding; @@ -32,138 +42,418 @@ public class CreateBlobOptions extends BlobServiceOptions { private Long sequenceNumber; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link CreateBlobOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CreateBlobOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ @Override public CreateBlobOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the <code>Content-Type</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>Content-Type</code> header value set, if any. + */ public String getContentType() { return contentType; } + /** + * Sets the optional <code>Content-Type</code> header value for the blob content. This value will be returned to + * clients in the headers of the response when the blob data or blob properties are requested. If no content type is + * specified, the default content type is <strong>application/octet-stream</strong>. + * + * @param contentType + * A {@link String} containing the <code>Content-Type</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setContentType(String contentType) { this.contentType = contentType; return this; } + /** + * Gets the <code>Content-Encoding</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>Content-Encoding</code> header value set, if any. + */ public String getContentEncoding() { return contentEncoding; } + /** + * Sets the optional <code>Content-Encoding</code> header value for the blob content. Use this value to specify the + * content encodings applied to the blob. This value will be returned to clients in the headers of the response when + * the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param contentEncoding + * A {@link String} containing the <code>Content-Encoding</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setContentEncoding(String contentEncoding) { this.contentEncoding = contentEncoding; return this; } + /** + * Gets the <code>Content-Language</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>Content-Language</code> header value set, if any. + */ public String getContentLanguage() { return contentLanguage; } + /** + * Sets the optional <code>Content-Language</code> header value for the blob content. Use this value to specify the + * content language of the blob. This value will be returned to clients in the headers of the response when the blob + * data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param contentLanguage + * A {@link String} containing the <code>Content-Language</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setContentLanguage(String contentLanguage) { this.contentLanguage = contentLanguage; return this; } + /** + * Gets the <code>Content-MD5</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>Content-MD5</code> header value set, if any. + */ public String getContentMD5() { return contentMD5; } + /** + * Sets the optional <code>Content-MD5</code> header value for the blob content. Use this value to specify an + * MD5 hash of the blob content. This hash is used to verify the integrity of the blob during transport. When this + * header is specified, the storage service checks the hash that has arrived with the one that was sent. If the two + * hashes do not match, the operation will fail with error code 400 (Bad Request). This value will be returned to + * clients in the headers of the response when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param contentMD5 + * A {@link String} containing the <code>Content-MD5</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; return this; } + /** + * Gets the <code>Cache-Control</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>Cache-Control</code> header value set, if any. + */ public String getCacheControl() { return cacheControl; } + /** + * Sets the optional <code>Cache-Control</code> header value for the blob content. The Blob service stores this + * value but does not use or modify it. This value will be returned to clients in the headers of the response when + * the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param cacheControl + * A {@link String} containing the <code>Cache-Control</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setCacheControl(String cacheControl) { this.cacheControl = cacheControl; return this; } + /** + * Gets the <code>x-ms-blob-content-type</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>x-ms-blob-content-type</code> header value set, if any. + */ public String getBlobContentType() { return blobContentType; } + /** + * Sets the optional <code>x-ms-blob-content-type</code> header value for the blob content. This value will be + * returned to clients in the headers of the response when the blob data or blob properties are requested. If no + * content type is specified, the default content type is <strong>application/octet-stream</strong>. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param blobContentType + * A {@link String} containing the <code>x-ms-blob-content-type</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setBlobContentType(String blobContentType) { this.blobContentType = blobContentType; return this; } + /** + * Gets the <code>x-ms-blob-content-encoding</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>x-ms-blob-content-encoding</code> header value set, if any. + */ public String getBlobContentEncoding() { return blobContentEncoding; } + /** + * Sets the optional <code>x-ms-blob-content-encoding</code> header value for the blob content. Use this value to + * specify the content encodings applied to the blob. This value will be returned to clients in the headers of the + * response when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param blobContentEncoding + * A {@link String} containing the <code>x-ms-blob-content-encoding</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setBlobContentEncoding(String blobContentEncoding) { this.blobContentEncoding = blobContentEncoding; return this; } + /** + * Gets the <code>x-ms-blob-content-language</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>x-ms-blob-content-language</code> header value set, if any. + */ public String getBlobContentLanguage() { return blobContentLanguage; } + /** + * Sets the optional <code>x-ms-blob-content-language</code> header value for the blob content. Use this value to + * specify the content language of the blob. This value will be returned to clients in the headers of the response + * when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param blobContentLanguage + * A {@link String} containing the <code>x-ms-blob-content-language</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setBlobContentLanguage(String blobContentLanguage) { this.blobContentLanguage = blobContentLanguage; return this; } + /** + * Gets the <code>x-ms-blob-content-md5</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>x-ms-blob-content-md5</code> header value set, if any. + */ public String getBlobContentMD5() { return blobContentMD5; } + /** + * Sets the optional MD5 hash value for the blob content. This value will be returned to clients in the + * <code>x-ms-blob-content-md5</code> header value of the response when the blob data or blob properties are + * requested. This hash is used to verify the integrity of the blob during transport. When this header is specified, + * the storage service checks the hash of the content that has arrived with the one that was sent. If the two hashes + * do not match, the operation will fail with error code 400 (Bad Request), which will cause a ServiceException to + * be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param blobContentMD5 + * A {@link String} containing the <code>x-ms-blob-content-md5</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setBlobContentMD5(String blobContentMD5) { this.blobContentMD5 = blobContentMD5; return this; } + /** + * Gets the <code>x-ms-blob-cache-control</code> header value set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the <code>x-ms-blob-cache-control</code> header value set, if any. + */ public String getBlobCacheControl() { return blobCacheControl; } + /** + * Sets the optional <code>x-ms-blob-cache-control</code> header value for the blob content. The Blob service stores + * this value but does not use or modify it. This value will be returned to clients in the headers of the response + * when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param blobCacheControl + * A {@link String} containing the <code>x-ms-blob-cache-control</code> header value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setBlobCacheControl(String blobCacheControl) { this.blobCacheControl = blobCacheControl; return this; } + /** + * Gets the blob metadata collection set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata set, if any. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Sets the blob metadata collection to associate with the created blob. Metadata is a collection of name-value + * {@link String} pairs for client use and is opaque to the server. Metadata names must adhere to the naming rules + * for <a href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * <p> + * The <em>metadata</em> value only affects calls made on methods where this {@link CreateBlobOptions} instance is + * passed as a parameter. + * + * @param metadata + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; return this; } + /** + * Adds a name-value pair to the blob metadata collection associated with this {@link CreateBlobOptions} instance. + * + * @param key + * A {@link String} containing the name portion of the name-value pair to add to the metadata collection. + * @param value + * A {@link String} containing the value portion of the name-value pair to add to the metadata + * collection. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions addMetadata(String key, String value) { this.getMetadata().put(key, value); return this; } + /** + * Gets the lease ID for the blob set in this {@link CreateBlobOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when updating the blob. This value is not used when creating a blob. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the sequence number set in this {@link CreateBlobOptions} instance. + * + * @return + * The page blob sequence number value set. + */ public Long getSequenceNumber() { return sequenceNumber; } + /** + * Sets the optional sequence number for a page blob in this {@link CreateBlobOptions} instance. This value is not + * used for block blobs. The sequence number is a user-controlled value that you can use to track requests. The + * value of the sequence number must be between 0 and 2^63 - 1. The default value is 0. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobOptions} instance is passed + * as a parameter. + * + * @param sequenceNumber + * The page blob sequence number value to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setSequenceNumber(Long sequenceNumber) { this.sequenceNumber = sequenceNumber; return this; } + /** + * Gets the access conditions set in this {@link CreateBlobOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for updating a blob. This value is not used when creating a blob. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link CreateBlobOptions} instance. + */ public CreateBlobOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java index cbbde22e0f99b..dba32e8f767ff 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -19,6 +19,17 @@ public class CreateBlobPagesOptions extends BlobServiceOptions { private String contentMD5; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link CreateBlobPagesOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CreateBlobPagesOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CreateBlobPagesOptions} instance. + */ @Override public CreateBlobPagesOptions setTimeout(Integer timeout) { super.setTimeout(timeout); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesResult.java index b7eb8e83f56df..20e98bd3f3cc0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesResult.java @@ -2,54 +2,137 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Put Page operation. This is returned by + * calls to implementations of {@link BlobContract#clearBlobPages(String, String, PageRange)}, + * {@link BlobContract#clearBlobPages(String, String, PageRange, CreateBlobPagesOptions)}, + * {@link BlobContract#createBlobPages(String, String, PageRange, long, java.io.InputStream)} and + * {@link BlobContract#createBlobPages(String, String, PageRange, long, java.io.InputStream, CreateBlobPagesOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691975.aspx">Put Page</a> documentation on + * MSDN for details of the underlying Blob Service REST API operation. + */ public class CreateBlobPagesResult { private String etag; private Date lastModified; private String contentMD5; private long sequenceNumber; + /** + * Gets the ETag of the blob. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> element returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time of the blob. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the page blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the MD5 hash value of the page blob. This value can be used to determine if the blob content has been + * corrupted in transmission. + * + * @return + * A {@link String} containing the MD5 hash value of the page blob. + */ public String getContentMD5() { return contentMD5; } + /** + * Reserved for internal use. Sets the MD5 hash value of the page blob from the <strong>Content-MD5</strong> element + * returned in the response. + * + * @param contentMD5 + * A {@link String} containing the MD5 hash value of the page blob. + */ public void setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; } + /** + * Gets the sequence number of the page blob. This value can be used when updating or deleting a page blob using an + * optimistic concurrency model to prevent the client from modifying data that has been changed by another + * client. + * + * @return + * A {@link String} containing the client-assigned sequence number value for the page blob. + */ public long getSequenceNumber() { return sequenceNumber; } + /** + * Reserved for internal use. Sets the sequence number of the page blob from the + * <strong>x-ms-blob-sequence-number</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param sequenceNumber + * A {@link String} containing the client-assigned sequence number value for the page blob. + */ public void setSequenceNumber(long sequenceNumber) { this.sequenceNumber = sequenceNumber; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotOptions.java index 35ffd6419f97a..4df9b3b1f7c57 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotOptions.java @@ -2,58 +2,146 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#createBlobSnapshot(String, String, CreateBlobSnapshotOptions) createBlobSnapshot} request. These + * options include an optional server timeout for the operation, blob metadata to set on the snapshot, a blob lease ID + * to get a blob with an active lease, an optional start and end range for blob content to + * return, and any access conditions to satisfy. + */ public class CreateBlobSnapshotOptions extends BlobServiceOptions { private HashMap<String, String> metadata = new HashMap<String, String>(); private String leaseId; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link CreateBlobSnapshotOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CreateBlobSnapshotOptions} + * instance is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CreateBlobSnapshotOptions} instance. + */ @Override public CreateBlobSnapshotOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the metadata collection set in this {@link CreateBlobSnapshotOptions} instance. + * + * @return + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata set, if any. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Sets the metadata collection to associate with a snapshot. Metadata is a collection of name-value {@link String} + * pairs for client use and is opaque to the server. Metadata names must adhere to the naming rules for <a + * href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# identifiers</a>. + * <p> + * The <em>metadata</em> value only affects calls made on methods where this {@link CreateBlobSnapshotOptions} + * instance is passed as a parameter. + * + * @param metadata + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values of + * the metadata to set. + * @return + * A reference to this {@link CreateBlobSnapshotOptions} instance. + */ public CreateBlobSnapshotOptions setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; return this; } + /** + * Adds a name-value pair to the metadata collection associated with this {@link CreateBlobSnapshotOptions} + * instance. + * + * @param key + * A {@link String} containing the name portion of the name-value pair to add to the metadata collection. + * @param value + * A {@link String} containing the value portion of the name-value pair to add to the metadata + * collection. + * @return + * A reference to this {@link CreateBlobSnapshotOptions} instance. + */ public CreateBlobSnapshotOptions addMetadata(String key, String value) { this.getMetadata().put(key, value); return this; } + /** + * Gets the lease ID to match for the blob set in this {@link CreateBlobSnapshotOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when creating a snapshot of the blob. If set, the lease must be active + * and the value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobSnapshotOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CreateBlobSnapshotOptions} instance. + */ public CreateBlobSnapshotOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the access conditions set in this {@link CreateBlobSnapshotOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for creating a snapshot of the blob. The operation will return an error if the + * access conditions are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobSnapshotOptions} instance is + * passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link CreateBlobSnapshotOptions} instance. + */ public CreateBlobSnapshotOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java index eeaf0fb61b377..25daaff571ab7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java @@ -2,45 +2,117 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Snapshot Blob operation. This is returned by + * calls to implementations of {@link BlobContract#createBlobSnapshot(String, String)} and + * {@link BlobContract#createBlobSnapshot(String, String, CreateBlobSnapshotOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691971.aspx">Snapshot Blob</a> documentation + * on MSDN for details of the underlying Blob Service REST API operation. + */ public class CreateBlobSnapshotResult { private String snapshot; private String etag; private Date lastModified; + /** + * Gets the snapshot timestamp value returned by the server to uniquely identify the newly created snapshot. + * <p> + * The snapshot timestamp value is an opaque value returned by the server to uniquely identify a snapshot version, + * and may be used in subsequent requests to access the snapshot. + * <p> + * + * @return + * A {@link String} containing the snapshot timestamp value of the newly created snapshot. + */ public String getSnapshot() { return snapshot; } + /** + * Reserved for internal use. Sets the snapshot timestamp value from the <strong>x-ms-snapshot</strong> header + * returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the newly created snapshot. + */ public void setSnapshot(String snapshot) { this.snapshot = snapshot; } + /** + * Gets the ETag of the snapshot. + * <p> + * Note that a snapshot cannot be written to, so the ETag of a given snapshot will never change. However, the ETag + * of the snapshot will differ from that of the base blob if new metadata was supplied with the create blob snapshot + * request. If no metadata was specified with the request, the ETag of the snapshot will be identical to that of the + * base blob at the time the snapshot was taken. + * + * @return + * A {@link String} containing the server-assigned ETag value for the snapshot. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the snapshot from the <strong>ETag</strong> header returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time of the snapshot. + * <p> + * Note that a snapshot cannot be written to, so the last modified time of a given snapshot will never change. + * However, the last modified time of the snapshot will differ from that of the base blob if new metadata was + * supplied with the create blob snapshot request. If no metadata was specified with the request, the last modified + * time of the snapshot will be identical to that of the base blob at the time the snapshot was taken. + * + * @return + * A {@link java.util.Date} containing the last modified time of the snapshot. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the snapshot from the <strong>Last-Modified</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the snapshot. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateContainerOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateContainerOptions.java index 2ad569ba45423..b128296b61aab 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateContainerOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateContainerOptions.java @@ -2,48 +2,141 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a {@link BlobContract#createContainer(String, CreateContainerOptions) + * createContainer} request. These options include a server response timeout for the request, metadata to set on the + * container, and the public access level for container and blob data. Options that are not set will not be passed to + * the server with a request. + */ public class CreateContainerOptions extends BlobServiceOptions { private String publicAccess; private HashMap<String, String> metadata = new HashMap<String, String>(); + /** + * Sets the server request timeout value associated with this {@link CreateContainerOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link CreateContainerOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link CreateContainerOptions} instance. + */ @Override public CreateContainerOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the metadata collection associated with this {@link CreateContainerOptions} instance. + * + * @return + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values of + * the container metadata to set. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Sets the metadata collection associated with this {@link CreateContainerOptions} instance. Metadata is a + * collection of name-value {@link String} pairs for client use and is opaque to the server. Metadata names must + * adhere to the naming rules for <a href="http://msdn.microsoft.com/en-us/library/aa664670(VS.71).aspx">C# + * identifiers</a>. + * <p> + * The <em>metadata</em> value only affects calls made on methods where this {@link CreateContainerOptions} instance + * is passed as a parameter. + * + * @param metadata + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values of + * the container metadata to set. + * @return + * A reference to this {@link CreateContainerOptions} instance. + */ public CreateContainerOptions setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; return this; } + /** + * Adds a metadata name-value pair to the metadata collection associated with this {@link CreateContainerOptions} + * instance. + * + * @param key + * A {@link String} containing the name portion of the name-value pair to add to the metadata collection. + * @param value + * A {@link String} containing the value portion of the name-value pair to add to the metadata + * collection. + * @return + * A reference to this {@link CreateContainerOptions} instance. + */ public CreateContainerOptions addMetadata(String key, String value) { this.getMetadata().put(key, value); return this; } + /** + * Gets the public access level value associated with this {@link CreateContainerOptions} instance. The public + * access level specifies whether data in the container may be accessed publicly and the level of access. Possible + * values include: + * <ul> + * <li><em>container</em>  Specifies full public read access for container and blob data. Clients can + * enumerate blobs within the container via anonymous request, but cannot enumerate containers within the storage + * account.</li> + * <li><em>blob</em>  Specifies public read access for blobs. Blob data within this container can be read + * via anonymous request, but container data is not available. Clients cannot enumerate blobs within the container + * via anonymous request.</li> + * </ul> + * The default value of <code>null</code> sets the container data private to the storage account owner. + * + * @return + * A {@link String} containing the public access level value to set, or <code>null</code>. + */ public String getPublicAccess() { return publicAccess; } + /** + * Sets the public access level value associated with this {@link CreateContainerOptions} instance. The public + * access level specifies whether data in the container may be accessed publicly and the level of access. Possible + * values include: + * <ul> + * <li><em>container</em>  Specifies full public read access for container and blob data. Clients can + * enumerate blobs within the container via anonymous request, but cannot enumerate containers within the storage + * account.</li> + * <li><em>blob</em>  Specifies public read access for blobs. Blob data within this container can be read + * via anonymous request, but container data is not available. Clients cannot enumerate blobs within the container + * via anonymous request.</li> + * </ul> + * The default value of <code>null</code> sets the container data private to the storage account owner. + * <p> + * The <em>publicAccess</em> value only affects calls made on methods where this {@link CreateContainerOptions} + * instance is passed as a parameter. + * + * @param publicAccess + * A {@link String} containing the public access level value to set, or <code>null</code> to set the + * container data private to the storage account owner. + * @return + * A reference to this {@link CreateContainerOptions} instance. + */ public CreateContainerOptions setPublicAccess(String publicAccess) { this.publicAccess = publicAccess; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteBlobOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteBlobOptions.java index 729a68c9e99f9..7d0d4377bdfc7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteBlobOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteBlobOptions.java @@ -2,61 +2,168 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Represents the options that may be set on a {@link BlobContract#deleteBlob(String, String, DeleteBlobOptions) + * deleteBlob} request. These options include an optional server timeout for the operation, a snapshot timestamp to + * specify an individual snapshot to delete, a blob lease ID to delete a blob with an active lease, a flag indicating + * whether to delete all snapshots but not the blob, or both the blob and all snapshots, and any access conditions to + * satisfy. + */ public class DeleteBlobOptions extends BlobServiceOptions { private String snapshot; private String leaseId; private boolean deleteSnaphotsOnly; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link DeleteBlobOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link DeleteBlobOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link DeleteBlobOptions} instance. + */ @Override public DeleteBlobOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the snapshot timestamp value set in this {@link DeleteBlobOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get. + */ public String getSnapshot() { return snapshot; } + /** + * Reserved for future use. Sets an optional snapshot timestamp value used to identify the particular snapshot of + * the blob to delete. + * <p> + * The snapshot timestamp value is an opaque value returned by the server to identify a snapshot. This option cannot + * be set if the delete snapshots only option is set to <code>true</code> or <code>false</code>. + * <p> + * Note that this value only affects calls made on methods where this {@link DeleteBlobOptions} instance is passed + * as a parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get. + * @return + * A reference to this {@link DeleteBlobOptions} instance. + */ public DeleteBlobOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the lease ID to match for the blob set in this {@link DeleteBlobOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when deleting the blob. If set, the lease must be active and the value + * must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link DeleteBlobOptions} instance is passed + * as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link DeleteBlobOptions} instance. + */ public DeleteBlobOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the flag indicating whether to delete only snapshots of the blob, or both the blob and all its snapshots set + * in this {@link DeleteBlobOptions} instance. + * + * @return + * A value of <code>true</code> to delete only the snapshots, or <code>false</code> to delete both snapshots + * and the blob. + */ public boolean getDeleteSnaphotsOnly() { return deleteSnaphotsOnly; } + /** + * Sets a flag indicating whether to delete only snapshots of the blob, or both the blob and all its snapshots. + * <p> + * If the <em>deleteSnaphotsOnly</em> parameter is set to <code>true</code>, only the snapshots of the blob are + * deleted by the operation. If the parameter is set to <code>false</code>, both the blob and all its snapshots are + * deleted by the operation. If this option is not set on a request, and the blob has associated snapshots, the Blob + * service returns a 409 (Conflict) status code and a {@link ServiceException} is thrown. + * <p> + * This option is not compatible with the snapshot option; if both are set the Blob service returns status code 400 + * (Bad Request) and a {@link StorageException} is thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link DeleteBlobOptions} instance is passed + * as a parameter. + * + * @param deleteSnaphotsOnly + * Set to <code>true</code> to delete only the snapshots, or <code>false</code> to delete both snapshots + * and the blob. + * @return + * A reference to this {@link DeleteBlobOptions} instance. + */ public DeleteBlobOptions setDeleteSnaphotsOnly(boolean deleteSnaphotsOnly) { this.deleteSnaphotsOnly = deleteSnaphotsOnly; return this; } + /** + * Gets the access conditions set in this {@link DeleteBlobOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for getting the blob. The operation will return an error if the access conditions + * are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link DeleteBlobOptions} instance is passed + * as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link DeleteBlobOptions} instance. + */ public DeleteBlobOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteContainerOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteContainerOptions.java index 1916ecb70320c..fc5c922de7566 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteContainerOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/DeleteContainerOptions.java @@ -2,31 +2,72 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a {@link BlobContract#deleteContainer(String, DeleteContainerOptions) + * deleteContainer} request. These options include a server response timeout for the request and access conditions that + * specify whether to perform the operation or not depending on the values of the Etag or last modified time of the + * container. Options that are not set will not be passed to the server with a request. + */ public class DeleteContainerOptions extends BlobServiceOptions { private AccessCondition accessCondition; + /** + * Sets the server request timeout value associated with this {@link DeleteContainerOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link DeleteContainerOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link DeleteContainerOptions} instance. + */ @Override public DeleteContainerOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the access conditions associated with this {@link DeleteContainerOptions} instance. + * + * @return + * An {@link AccessCondition} reference containing the Etag and last modified time conditions for performing + * the delete container operation, or <code>null</code> if not set. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions associated with this {@link DeleteContainerOptions} instance. By default, the delete + * container operation will delete the container unconditionally. Use this method to specify conditions on the Etag + * or last modified time value for performing the delete container operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this {@link DeleteContainerOptions} + * instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} reference containing the Etag and last modified time conditions for + * performing the delete container operation. Specify <code>null</code> to make the operation + * unconditional. + * @return + * A reference to this {@link DeleteContainerOptions} instance. + */ public DeleteContainerOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataOptions.java index 9b1e055d8ae8f..0c8cbbbc88f07 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataOptions.java @@ -2,51 +2,122 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#getBlobMetadata(String, String, GetBlobMetadataOptions) getBlobMetadata} request. + * These options include an optional server timeout for the operation, the lease ID if the blob has an active lease, the + * snapshot timestamp to get the properties of a snapshot, and any access conditions for the request. + */ public class GetBlobMetadataOptions extends BlobServiceOptions { private String snapshot; private String leaseId; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link GetBlobMetadataOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link GetBlobMetadataOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link GetBlobMetadataOptions} instance. + */ @Override public GetBlobMetadataOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the snapshot timestamp value set in this {@link GetBlobMetadataOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get metadata for. + */ public String getSnapshot() { return snapshot; } + /** + * Sets the snapshot timestamp value used to identify the particular snapshot of the blob to get metadata for. The + * snapshot timestamp value is an opaque value returned by the server to identify a snapshot. When this option is + * set, only the metadata of the snapshot is returned. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobMetadataOptions} instance is + * passed as a parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get metadata for. + * @return + * A reference to this {@link GetBlobMetadataOptions} instance. + */ public GetBlobMetadataOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the lease ID to match for the blob set in this {@link GetBlobMetadataOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when getting the metadata of the blob. If set, the lease must be active and the + * value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobMetadataOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link GetBlobMetadataOptions} instance. + */ public GetBlobMetadataOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the access conditions set in this {@link GetBlobMetadataOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for getting the metadata of the blob. The operation will return an error if the + * access conditions are not met. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link GetBlobMetadataOptions} instance. + */ public GetBlobMetadataOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java index 93348f23cdba6..a31af10c3dc9b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java @@ -2,46 +2,115 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Blob Metadata operation. This is + * returned by calls to implementations of {@link BlobContract#getBlobMetadata(String, String)} and + * {@link BlobContract#getBlobMetadata(String, String, GetBlobMetadataOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179350.aspx">Get Blob Metadata</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class GetBlobMetadataResult { private String etag; private Date lastModified; private HashMap<String, String> metadata; + /** + * Gets the ETag of the blob. For block blobs, this value is returned only if the blob has committed blocks. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time of the block blob. For block blobs, this value is returned only if the blob has + * committed blocks. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the user-defined blob metadata as a map of name and value pairs. The metadata is for client use and is + * opaque to the server. + * + * @return + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * the blob metadata. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param metadata + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * of the blob metadata. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobOptions.java index e35db2b0c05f6..2edeca6dc264d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobOptions.java @@ -2,18 +2,27 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.core.ServiceException; + +/** + * Represents the options that may be set on a {@link BlobContract#getBlob(String, String, GetBlobOptions) getBlob} + * request. These options include an optional server timeout for the operation, a snapshot timestamp to specify a + * snapshot, a blob lease ID to get a blob with an active lease, an optional start and end range for blob content to + * return, and any access conditions to satisfy. + */ public class GetBlobOptions extends BlobServiceOptions { private String snapshot; private String leaseId; @@ -22,61 +31,193 @@ public class GetBlobOptions extends BlobServiceOptions { private Long rangeEnd; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link GetBlobOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link GetBlobOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ @Override public GetBlobOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the snapshot timestamp value set in this {@link GetBlobOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get. + */ public String getSnapshot() { return snapshot; } + /** + * Sets an optional snapshot timestamp value used to identify the particular snapshot of the blob to get properties, + * metadata, and content for. The snapshot timestamp value is an opaque value returned by the server to identify a + * snapshot. When this option is set, only the properties, metadata, and content of the snapshot are returned. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobOptions} instance is passed as a + * parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the lease ID to match for the blob set in this {@link GetBlobOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when getting the blob. If set, the lease must be active and the value + * must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobOptions} instance is passed as a + * parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Reserved for future use. Gets a flag indicating whether to return an MD5 hash for the specified range of blob + * content set in this {@link GetBlobOptions} instance. + * + * @return + * A flag value of <code>true</code> to get the MD5 hash value for the specified range, otherwise + * <code>false</code>. + */ public boolean isComputeRangeMD5() { return computeRangeMD5; } + /** + * Reserved for future use. Sets a flag indicating whether to return an MD5 hash for the specified range of blob + * content. + * <p> + * When the <em>computeRangeMD5</em> parameter is set to <code>true</code> and specified together with a range less + * than or equal to 4 MB in size, the get blob operation response includes the MD5 hash for the range. If the + * <em>computeRangeMD5</em> parameter is set to <code>true</code> and no range is specified or the range exceeds 4 + * MB in size, a {@link ServiceException} is thrown. + * + * @param computeRangeMD5 + * Reserved for future use. Set a flag value of <code>true</code> to get the MD5 hash value for the + * specified range, otherwise <code>false</code>. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setComputeRangeMD5(boolean computeRangeMD5) { this.computeRangeMD5 = computeRangeMD5; return this; } + /** + * Gets the beginning byte offset value of the blob content range to return set in this {@link GetBlobOptions} + * instance. + * + * @return + * The beginning offset value in bytes for the blob content range to return. + */ public Long getRangeStart() { return rangeStart; } + /** + * Sets an optional beginning byte offset value of the blob content range to return for the request, inclusive. + * <p> + * When this value is set, the blob content beginning at the byte offset specified by the <em>rangeStart</em> value + * and ending at the range end value, inclusive, is returned in the server response to the get blob operation. If + * the range end is not set, the response includes blob content from the <em>rangeStart</em> value to the end of the + * blob. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobOptions} instance is passed as a + * parameter. + * + * @param rangeStart + * The beginning offset value in bytes for the blob content range to return, inclusive. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setRangeStart(Long rangeStart) { this.rangeStart = rangeStart; return this; } + /** + * Gets the ending byte offset value for the blob content range to return set in this {@link GetBlobOptions} + * instance. + * + * @return + * The ending offset value in bytes for the blob content range to return. + */ public Long getRangeEnd() { return rangeEnd; } + /** + * Sets an optional ending byte offset value of the blob content range to return for the request, inclusive. + * <p> + * If the range start is not set, this value is ignored and the response includes content from the entire blob. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobOptions} instance is passed as a + * parameter. + * + * @param rangeEnd + * The ending offset value in bytes for the blob content range to return, inclusive. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setRangeEnd(Long rangeEnd) { this.rangeEnd = rangeEnd; return this; } + /** + * Gets the access conditions set in this {@link GetBlobOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for getting the blob. The operation will return an error if the access conditions + * are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobOptions} instance is passed as a + * parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link GetBlobOptions} instance. + */ public GetBlobOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesOptions.java index 8c252723fc180..1e93569b8a77f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesOptions.java @@ -2,51 +2,122 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#getBlobProperties(String, String, GetBlobPropertiesOptions) getBlobProperties} request. + * These options include an optional server timeout for the operation, the lease ID if the blob has an active lease, the + * snapshot timestamp to get the properties of a snapshot, and any access conditions for the request. + */ public class GetBlobPropertiesOptions extends BlobServiceOptions { private String snapshot; private String leaseId; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link GetBlobPropertiesOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link GetBlobPropertiesOptions} + * instance is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link GetBlobPropertiesOptions} instance. + */ @Override public GetBlobPropertiesOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the snapshot timestamp value set in this {@link GetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get properties for. + */ public String getSnapshot() { return snapshot; } + /** + * Sets the snapshot timestamp value used to identify the particular snapshot of the blob to get properties for. The + * snapshot timestamp value is an opaque value returned by the server to identify a snapshot. When this option is + * set, only the properties of the snapshot are returned. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get properties for. + * @return + * A reference to this {@link GetBlobPropertiesOptions} instance. + */ public GetBlobPropertiesOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the lease ID to match for the blob set in this {@link GetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when getting the properties of the blob. If set, the lease must be active and the + * value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link GetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link GetBlobPropertiesOptions} instance. + */ public GetBlobPropertiesOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the access conditions set in this {@link GetBlobPropertiesOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for getting the properties of the blob. The operation will return an error if the + * access conditions are not met. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link GetBlobPropertiesOptions} instance. + */ public GetBlobPropertiesOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java index 9d20e5221e945..db77763781ff5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java @@ -2,36 +2,80 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Blob Properties operation. This is + * returned by calls to implementations of {@link BlobContract#getBlobProperties(String, String)} and + * {@link BlobContract#getBlobProperties(String, String, GetBlobPropertiesOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179394.aspx">Get Blob Properties</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class GetBlobPropertiesResult { private BlobProperties properties; private HashMap<String, String> metadata = new HashMap<String, String>(); + /** + * Gets the standard HTTP properties and system properties of the blob. + * + * @return + * A {@link BlobProperties} instance containing the properties of the blob. + */ public BlobProperties getProperties() { return properties; } + /** + * Reserved for internal use. Sets the blob properties from the headers returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param properties + * A {@link BlobProperties} instance containing the properties of the blob. + */ public void setProperties(BlobProperties properties) { this.properties = properties; } + /** + * Gets the user-defined blob metadata as a map of name and value pairs. The metadata is for client use and is + * opaque to the server. + * + * @return + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * the blob metadata. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param metadata + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * of the blob metadata. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java index b29a99fee7a27..98a86f38291e7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java @@ -2,46 +2,104 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.io.InputStream; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Blob operation. This is returned by calls + * to implementations of {@link BlobContract#getBlob(String, String, GetBlobOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179440.aspx">Get Blob</a> documentation on + * MSDN for details of the underlying Blob Service REST API operation. + */ public class GetBlobResult { private InputStream contentStream; private BlobProperties properties; private HashMap<String, String> metadata; + /** + * Gets the content of the blob. + * + * @return + * An {@link InputStream} instance containing the content of the blob. + */ public InputStream getContentStream() { return contentStream; } + /** + * Reserved for internal use. Sets the blob content from the body returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param contentStream + * An {@link InputStream} instance containing the content of the blob. + */ public void setContentStream(InputStream contentStream) { this.contentStream = contentStream; } + /** + * Gets the standard HTTP properties and system properties of the blob. + * + * @return + * A {@link BlobProperties} instance containing the properties of the blob. + */ public BlobProperties getProperties() { return properties; } + /** + * Reserved for internal use. Sets the blob properties from the headers returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param properties + * A {@link BlobProperties} instance containing the properties of the blob. + */ public void setProperties(BlobProperties properties) { this.properties = properties; } + /** + * Gets the user-defined blob metadata as a map of name and value pairs. The metadata is for client use and is + * opaque to the server. + * + * @return + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * the blob metadata. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param metadata + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * of the blob metadata. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerACLResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerACLResult.java index b0277c1b0bac0..97597f8dbc016 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerACLResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerACLResult.java @@ -2,25 +2,54 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Container ACL operation. This is returned + * by calls to implementations of {@link BlobContract#getContainerACL(String)} and + * {@link BlobContract#getContainerACL(String, BlobServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179469.aspx">Get Container ACL</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class GetContainerACLResult { private ContainerACL containerACL; + /** + * Gets the container's public access level and container-level access policies from the headers and body returned + * in the response. + * + * @return + * A {@link ContainerACL} instance representing the public access level and container-level access policies + * returned by the request. + */ public ContainerACL getContainerACL() { return containerACL; } + /** + * Reserved for internal use. Sets the container's public access level and container-level access policies from the + * headers and body returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param containerACL + * A {@link ContainerACL} instance representing the public access level and container-level access + * policies returned by the request. + */ public void setValue(ContainerACL containerACL) { this.containerACL = containerACL; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java index 24c6d318e4995..00ad2de3de558 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java @@ -2,46 +2,114 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Container Properties and Get Container + * Metadata operations. This is returned by + * calls to implementations of {@link BlobContract#getContainerProperties(String)}, + * {@link BlobContract#getContainerProperties(String, BlobServiceOptions)}, + * {@link BlobContract#getContainerMetadata(String)}, and + * {@link BlobContract#getContainerMetadata(String, BlobServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179370.aspx">Get Container Properties</a> and + * <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691976.aspx">Get Container Metadata</a> documentation + * on MSDN for details of the underlying Blob Service REST API operations. + */ public class GetContainerPropertiesResult { private String etag; private Date lastModified; private HashMap<String, String> metadata; + /** + * Gets the Etag of the container. This value can be used when updating or deleting a container using an optimistic + * concurrency model to prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned Etag value for the container. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the Etag of the container from the <strong>ETag</strong> header returned in + * the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned Etag value for the container. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modifed time of the container. This value can be used when updating or deleting a container using + * an optimistic concurrency model to prevent the client from modifying data that has been changed by another + * client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the container. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the container from the + * <strong>Last-Modified</strong> header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the container. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the container metadata as a map of name and value pairs. The container metadata is for client use and is + * opaque to the server. + * + * @return + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * the container metadata. + */ public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the container metadata from the <strong>x-ms-meta-<em>name</em></strong> headers + * returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param metadata + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * of the container metadata. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetServicePropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetServicePropertiesResult.java index 0a5a0e0f16f8b..c3773a69c6712 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetServicePropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetServicePropertiesResult.java @@ -2,25 +2,56 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the service properties returned in response to Blob Service REST API operations. This is + * returned by calls to implementations of {@link BlobContract#getServiceProperties()} and + * {@link BlobContract#getServiceProperties(BlobServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/hh452239.aspx">Get Blob Service Properties</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class GetServicePropertiesResult { private ServiceProperties value; + /** + * Gets a {@link ServiceProperties} instance containing the service property values associated with the storage + * account. + * <p> + * Modifying the values in the {@link ServiceProperties} instance returned does not affect the values associated + * with the storage account. To change the values in the storage account, call the + * {@link BlobContract#setServiceProperties} method and pass the modified {@link ServiceProperties} instance as a + * parameter. + * + * @return + * A {@link ServiceProperties} instance containing the property values associated with the storage account. + */ public ServiceProperties getValue() { return value; } + /** + * Reserved for internal use. Sets the value of the {@link ServiceProperties} instance associated with a + * storage service call result. This method is invoked by the API to store service properties returned by + * a call to a REST operation and is not intended for public use. + * + * @param value + * A {@link ServiceProperties} instance containing the property values associated with the storage + * account. + */ public void setValue(ServiceProperties value) { this.value = value; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksOptions.java index 624474ea57f74..13e7c9fe9e8e0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksOptions.java @@ -2,61 +2,156 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#listBlobBlocks(String, String, ListBlobBlocksOptions) listBlobBlocks} request. These options + * include an optional server timeout for the operation, the lease ID if the blob has an active lease, the snapshot + * timestamp to get the committed blocks of a snapshot, whether to return the committed block list, and whether to + * return the uncommitted block list. + */ public class ListBlobBlocksOptions extends BlobServiceOptions { private String leaseId; private String snapshot; private boolean committedList; private boolean uncommittedList; + /** + * Sets the optional server request timeout value associated with this {@link ListBlobBlocksOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ @Override public ListBlobBlocksOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the lease ID to match for the blob set in this {@link ListBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets a lease ID value to match when listing the blocks of the blob. If set, the lease must be active and the + * value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ public ListBlobBlocksOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the snapshot timestamp value set in this {@link ListBlobBlocksOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to list. + */ public String getSnapshot() { return snapshot; } + /** + * Sets the snapshot timestamp value used to identify the particular snapshot of the blob to list blocks for. The + * snapshot timestamp value is an opaque value returned by the server to identify a snapshot. When this option is + * set, only the list of committed blocks of the snapshot is returned. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to list. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ public ListBlobBlocksOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the flag value indicating whether to return the committed blocks of the blob set in this + * {@link ListBlobBlocksOptions} instance. + * + * @return + * A <code>boolean</code> flag value indicating whether to return the committed blocks of the blob. + */ public boolean isCommittedList() { return committedList; } + /** + * Sets a flag indicating whether to return the committed blocks of the blob in the response to the list blob blocks + * request. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param committedList + * Set to <code>true</code> to return the committed blocks of the blob; otherwise, <code>false</code>. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ public ListBlobBlocksOptions setCommittedList(boolean committedList) { this.committedList = committedList; return this; } + /** + * Gets the flag value indicating whether to return the uncommitted blocks of the blob set in this + * {@link ListBlobBlocksOptions} instance. + * + * @return + * A <code>boolean</code> flag value indicating whether to return the uncommitted blocks of the blob. + */ public boolean isUncommittedList() { return uncommittedList; } + /** + * Sets a flag indicating whether to return the uncommitted blocks of the blob in the response to the list blob + * blocks request. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobBlocksOptions} instance is + * passed as a parameter. + * + * @param uncommittedList + * Set to <code>true</code> to return the uncommitted blocks of the blob; otherwise, <code>false</code>. + * @return + * A reference to this {@link ListBlobBlocksOptions} instance. + */ public ListBlobBlocksOptions setUncommittedList(boolean uncommittedList) { this.uncommittedList = uncommittedList; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java index 125c5cb6934e9..43008a72f44d6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -23,8 +23,17 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import com.microsoft.windowsazure.services.blob.BlobContract; import com.microsoft.windowsazure.services.core.utils.pipeline.Base64StringAdapter; +/** + * A wrapper class for the response returned from a Blob Service REST API Get Block List operation. This is returned by + * calls to implementations of {@link BlobContract#listBlobBlocks(String, String)} and + * {@link BlobContract#listBlobBlocks(String, String, ListBlobBlocksOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179400.aspx">Get Block List</a> documentation + * on MSDN for details of the underlying Blob Service REST API operation. + */ @XmlRootElement(name = "BlockList") public class ListBlobBlocksResult { private Date lastModified; @@ -34,77 +43,214 @@ public class ListBlobBlocksResult { private List<Entry> committedBlocks = new ArrayList<Entry>(); private List<Entry> uncommittedBlocks = new ArrayList<Entry>(); + /** + * Gets the last modified time of the block blob. This value is returned only if the blob has committed blocks. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the ETag of the blob. This value is returned only if the blob has committed blocks. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the MIME content type of the blob. This value defaults to <strong>application/xml</strong>. + * + * @return + * A {@link String} containing the MIME content type value for the blob. + */ public String getContentType() { return contentType; } + /** + * Reserved for internal use. Sets the MIME content type of the blob from the <strong>Content-Type</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param contentType + * A {@link String} containing the MIME content type value for the blob. + */ public void setContentType(String contentType) { this.contentType = contentType; } + /** + * Gets the size of the blob in bytes. For blobs with no committed blocks, this value is 0. + * + * @return + * The size of the blob in bytes. + */ public long getContentLength() { return contentLength; } + /** + * Reserved for internal use. Sets the content length of the blob from the <strong>x-ms-blob-content-length</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param contentLength + * The size of the blob in bytes. + */ public void setContentLength(long contentLength) { this.contentLength = contentLength; } + /** + * Gets a list of the committed blocks of the blob. This list may be empty if no blocks have been committed or if + * committed blocks were not specified in the request. + * + * @return + * A {@link List} of {@link Entry} instances representing the committed blocks of the blob. + */ @XmlElementWrapper(name = "CommittedBlocks") @XmlElement(name = "Block") public List<Entry> getCommittedBlocks() { return committedBlocks; } + /** + * Reserved for internal use. Sets the list of the committed blocks of the blob from the <strong>Block</strong> + * elements in the <strong>CommittedBlocks</strong> element of the <strong>BlockList</strong> element in the + * response body returned by the server. + * + * @param committedBlocks + * A {@link List} of {@link Entry} instances representing the committed blocks of the blob. + */ public void setCommittedBlocks(List<Entry> committedBlocks) { this.committedBlocks = committedBlocks; } + /** + * Gets a list of the uncommitted blocks of the blob. This list may be empty if no uncommitted blocks are associated + * with the blob, or if uncommitted blocks were not specified in the {@link ListBlobBlocksOptions options} parameter + * of the request. + * + * @return + * A {@link List} of {@link Entry} instances representing the uncommitted blocks of the blob. + */ @XmlElementWrapper(name = "UncommittedBlocks") @XmlElement(name = "Block") public List<Entry> getUncommittedBlocks() { return uncommittedBlocks; } + /** + * Reserved for internal use. Sets the list of the uncommitted blocks of the blob from the <strong>Block</strong> + * elements in the <strong>UncommittedBlocks</strong> element of the <strong>BlockList</strong> element in the + * response body returned by the server. + * + * @param uncommittedBlocks + * A {@link List} of {@link Entry} instances representing the uncommitted blocks of the blob. + */ public void setUncommittedBlocks(List<Entry> uncommittedBlocks) { this.uncommittedBlocks = uncommittedBlocks; } + /** + * The class for an entry in a list of blocks, representing a committed or uncommitted block. + */ public static class Entry { private String blockId; private long blockLength; + /** + * Gets the client-specified block ID for a block list entry. + * + * @return + * A {@link String} containing the client-specified block ID for a block. + */ @XmlElement(name = "Name") @XmlJavaTypeAdapter(Base64StringAdapter.class) public String getBlockId() { return blockId; } + /** + * Reserved for internal use. Sets the client-specified block ID for a block list entry from the + * <strong>Name</strong> element in the <strong>Block</strong> element in the list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param blockId + * A {@link String} containing the client-specified block ID for the block. + */ public void setBlockId(String blockId) { this.blockId = blockId; } + /** + * Gets the length in bytes of a block list entry. + * + * @return + * The length of the block in bytes. + */ @XmlElement(name = "Size") public long getBlockLength() { return blockLength; } + /** + * Reserved for internal use. Sets the length in bytes of a block list entry from the <strong>Size</strong> + * element in the <strong>Block</strong> element in the list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param blockLength + * The length of the block in bytes. + */ public void setBlockLength(long blockLength) { this.blockLength = blockLength; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsOptions.java index 916981de10f51..c944d19e596c7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsOptions.java @@ -2,18 +2,27 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#listBlobRegions(String, String, ListBlobRegionsOptions) listBlobRegions} request. + * These options include an optional server timeout for the operation, the lease ID if the blob has an active lease, the + * snapshot timestamp to get the valid page ranges of a snapshot, the start offset and/or end offset to use to narrow + * the returned valid page range results, and any access conditions for the request. + */ public class ListBlobRegionsOptions extends BlobServiceOptions { private String leaseId; private String snapshot; @@ -21,52 +30,162 @@ public class ListBlobRegionsOptions extends BlobServiceOptions { private Long rangeEnd; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link ListBlobRegionsOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ @Override public ListBlobRegionsOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the lease ID to match for the blob set in this {@link ListBlobRegionsOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when getting the valid page ranges of the blob. If set, the lease must + * be active and the value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ public ListBlobRegionsOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the snapshot timestamp value set in this {@link ListBlobRegionsOptions} instance. + * + * @return + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get valid page ranges + * for. + */ public String getSnapshot() { return snapshot; } + /** + * Sets an optional snapshot timestamp value used to identify the particular snapshot of the blob to get valid page + * ranges for. The snapshot timestamp value is an opaque value returned by the server to identify a snapshot. When + * this option is set, only the valid page ranges of the snapshot are returned. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance is + * passed as a parameter. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp value of the blob snapshot to get valid page ranges + * for. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ public ListBlobRegionsOptions setSnapshot(String snapshot) { this.snapshot = snapshot; return this; } + /** + * Gets the beginning byte offset value of the valid page ranges to return set in this + * {@link ListBlobRegionsOptions} instance. + * + * @return + * The beginning offset value in bytes for the valid page ranges to return. + */ public Long getRangeStart() { return rangeStart; } + /** + * Sets an optional beginning byte offset value of the valid page ranges to return for the request, inclusive. + * <p> + * If the range end is not set, the response includes valid page ranges from the <em>rangeStart</em> value to the + * end of the blob. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance is + * passed as a parameter. + * + * @param rangeStart + * The beginning offset value in bytes for the valid page ranges to return, inclusive. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ public ListBlobRegionsOptions setRangeStart(Long rangeStart) { this.rangeStart = rangeStart; return this; } + /** + * Gets the ending byte offset value for the valid page ranges to return set in this {@link ListBlobRegionsOptions} + * instance. + * + * @return + * The ending offset value in bytes for the valid page ranges to return. + */ public Long getRangeEnd() { return rangeEnd; } + /** + * Sets an optional ending byte offset value of the valid page ranges to return for the request, inclusive. + * <p> + * If the range start is not set, this value is ignored and the response includes valid page ranges from the entire + * blob. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance is + * passed as a parameter. + * + * @param rangeEnd + * The ending offset value in bytes for the valid page ranges to return, inclusive. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ public ListBlobRegionsOptions setRangeEnd(Long rangeEnd) { this.rangeEnd = rangeEnd; return this; } + /** + * Gets the access conditions set in this {@link ListBlobRegionsOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for getting the valid page ranges of the blob. The operation will return an error + * if the access conditions are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link ListBlobRegionsOptions} instance is + * passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link ListBlobRegionsOptions} instance. + */ public ListBlobRegionsOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java index da1ea2e16854b..ffec19183345f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -20,6 +20,15 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Get Page Ranges operation. This is returned by + * calls to implementations of {@link BlobContract#listBlobRegions(String, String, ListBlobRegionsOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691973.aspx">Get Page Ranges</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ @XmlRootElement(name = "PageList") public class ListBlobRegionsResult { private Date lastModified; @@ -27,35 +36,108 @@ public class ListBlobRegionsResult { private long contentLength; private List<PageRange> pageRanges; + /** + * Gets the last modified time of the blob. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the ETag of the blob. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the size of the blob in bytes. + * + * @return + * The size of the blob in bytes. + */ public long getContentLength() { return contentLength; } + /** + * Reserved for internal use. Sets the content length of the blob from the <strong>x-ms-blob-content-length</strong> + * header returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param contentLength + * The size of the blob in bytes. + */ public void setContentLength(long contentLength) { this.contentLength = contentLength; } + /** + * Gets the list of non-overlapping valid page ranges in the blob that match the parameters of the request, sorted + * by increasing address page range. + * + * @return + * A {@link List} of {@link PageRange} instances containing the valid page ranges for the blob. + */ @XmlElement(name = "PageRange") public List<PageRange> getPageRanges() { return pageRanges; } + /** + * Reserved for internal use. Sets the list of valid page ranges in the blob that match the parameters of the + * request from the <strong>PageRange</strong> elements of the <strong>PageList</strong> element returned by the + * server in the response body. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param pageRanges + * A {@link List} of {@link PageRange} instances containing the valid page ranges for the blob. + */ public void setPageRanges(List<PageRange> pageRanges) { this.pageRanges = pageRanges; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java index d437f99b8ae7d..c16f54bdb6c10 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java @@ -2,18 +2,28 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.core.ServiceException; + +/** + * Represents the options that may be set on a {@link BlobContract#listBlobs(String, ListBlobsOptions)} request. These + * options include a server response timeout for the request, a prefix for blobs to match, a marker to + * continue a list operation, a maximum number of results to return with one list operation, a delimiter for + * structuring virtual blob hierarchies, and whether to include blob metadata, blob snapshots, and uncommitted blobs + * in the results. + */ public class ListBlobsOptions extends BlobServiceOptions { private String prefix; private String marker; @@ -23,70 +33,256 @@ public class ListBlobsOptions extends BlobServiceOptions { private boolean includeSnapshots; private boolean includeUncommittedBlobs; + /** + * Sets the optional server request timeout value associated with this {@link ListBlobsOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ @Override public ListBlobsOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the prefix filter associated with this {@link ListBlobsOptions} instance. This value is used to return only + * blobs beginning with the prefix from the container in methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @return + * A {@link String} containing the prefix used to filter the blob names returned, if any. + */ public String getPrefix() { return prefix; } + /** + * Sets the optional blob name prefix filter value to use in a request. If this value is set, the server will return + * only blob names that match the prefix value in the response. + * <p> + * The <em>prefix</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @param prefix + * A {@link String} containing a prefix to use to filter the blob names returned. + */ public ListBlobsOptions setPrefix(String prefix) { this.prefix = prefix; return this; } + /** + * Gets the marker value set in this {@link ListBlobsOptions} instance. If this value is set, the server will return + * blob names beginning at the specified marker in the response. + * + * @return + * A {@link String} containing the marker value to use to specify the beginning of the request results, if + * any. + */ public String getMarker() { return marker; } + /** + * Sets the optional marker value to use in a request. If this value is set, the server will return blob names + * beginning at the specified marker in the response. Leave this value unset for an initial request. + * <p> + * The List Blobs operation returns a marker value in a <strong>NextMarker</strong> element if the blob list + * returned is not complete. The marker value may then be used in a subsequent call to request the next set of blob + * list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method on a {@link ListBlobsResult} instance to get + * the marker value to set on a {@link ListBlobsOptions} instance using a call to this method. Pass the + * {@link ListBlobsOptions} instance as a parameter to a {@link BlobContract#listBlobs(String, ListBlobsOptions)} + * call to get the next portion of the blob list. + * <p> + * The <em>marker</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @param marker + * A {@link String} containing the marker value to use to specify the beginning of the request results. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setMarker(String marker) { this.marker = marker; return this; } + /** + * Gets the value of the maximum number of results to return set in this {@link ListBlobsOptions} instance. + * + * @return + * The maximum number of results to return. If the value is zero, the server will return up to 5,000 items. + */ public int getMaxResults() { return maxResults; } + /** + * Sets the optional maximum number of results to return for a request. If this value is set, the server will return + * up to this number of blob and blob prefix results in the response. If a value is not specified, or a value + * greater than 5,000 is specified, the server will return up to 5,000 items. + * <p> + * If there are more blobs and blob prefixes that can be returned than this maximum, the server returns a marker + * value in a <strong>NextMarker</strong> element in the response. The marker value may then be used in a subsequent + * call to request the next set of blob list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method on a {@link ListBlobsResult} instance to get + * the marker value to set on a {@link ListBlobsOptions} instance using a call to + * {@link ListBlobsOptions#setMarker(String)}. Pass the {@link ListBlobsOptions} instance as a parameter to a + * {@link BlobContract#listBlobs(String, ListBlobsOptions)} call to get the next portion of the blob list. + * <p> + * The <em>maxResults</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @param maxResults + * The maximum number of results to return. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setMaxResults(int maxResults) { this.maxResults = maxResults; return this; } + /** + * Gets the value of the delimiter to use for grouping virtual blob hierarchy set in this {@link ListBlobsOptions} + * instance. + * + * @return + * A {@link String} containing the delimiter value, if any. + */ public String getDelimiter() { return delimiter; } + /** + * Sets the value of the optional delimiter to use for grouping in a virtual blob hierarchy. + * <p> + * When the request includes this optional parameter, the operation returns a blob prefix in a + * <strong>BlobPrefix</strong> element in the response that acts as a placeholder for all blobs whose names begin + * with the same substring up to the appearance of the delimiter character. The delimiter may be a single character + * or a string. + * <p> + * The <em>delimiter</em> parameter enables the caller to traverse the blob namespace by using a user-configured + * delimiter. In this way, you can traverse a virtual hierarchy of blobs as though it were a file system. The + * delimiter is a string that may be one or more characters long. When the request includes this parameter, the + * operation response includes one <strong>BlobPrefix</strong> element for each set of blobs whose names begin with + * a common substring up to the first appearance of the delimiter string that comes after any prefix specified for + * the request. The value of the <strong>BlobPrefix</strong> element is <code>substring+delimiter</code>, where + * <code>substring</code> is the common substring that begins one or more blob names, and <code>delimiter</code> is + * the value of the delimiter parameter. + * <p> + * The <strong>BlobPrefix</strong> elements in the response are accessed with the + * {@link ListBlobsResult#getBlobPrefixes()} method. You can use each value in the list returned to make a list + * blobs request for the blobs that begin with that blob prefix value, by specifying the value as the prefix option + * with a call to the {@link ListBlobsOptions#setPrefix(String) setPrefix} method on a {@link ListBlobsOptions} + * instance passed as a parameter to the request. + * <p> + * Note that each blob prefix returned in the response counts toward the maximum number of blob results, just as + * each blob does. + * <p> + * Note that if a delimiter is set, you cannot include snapshots. A request that includes both returns an + * InvalidQueryParameter error (HTTP status code 400 – Bad Request), which causes a {@link ServiceException} to be + * thrown. + * <p> + * The <em>delimiter</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is + * passed as a parameter. + * + * @param delimiter + * A {@link String} containing the delimiter value to use for grouping virtual blob hierarchy. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setDelimiter(String delimiter) { this.delimiter = delimiter; return this; } + /** + * Gets the value of a flag indicating whether to include blob metadata with a response set in this + * {@link ListBlobsOptions} instance. + * + * @return + * A value of <code>true</code> to include blob metadata with a response, otherwise, <code>false</code>. + */ public boolean isIncludeMetadata() { return includeMetadata; } + /** + * Sets the value of an optional flag indicating whether to include blob metadata with a response. + * + * @param includeMetadata + * Set a value of <code>true</code> to include blob metadata with a response, otherwise, + * <code>false</code>. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setIncludeMetadata(boolean includeMetadata) { this.includeMetadata = includeMetadata; return this; } + /** + * Gets the value of a flag indicating whether to include blob snapshots with a response set in this + * {@link ListBlobsOptions} instance. + * + * @return + * A value of <code>true</code> to include blob metadata with a response, otherwise, <code>false</code>. + */ public boolean isIncludeSnapshots() { return includeSnapshots; } + /** + * Sets the value of an optional flag indicating whether to include blob snapshots with a response. + * <p> + * Note that if this flag is set, you cannot set a delimiter. A request that includes both returns an + * InvalidQueryParameter error (HTTP status code 400 – Bad Request), which causes a {@link ServiceException} to be + * thrown. + * + * @param includeSnapshots + * Set a value of <code>true</code> to include blob metadata with a response, otherwise, + * <code>false</code>. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setIncludeSnapshots(boolean includeSnapshots) { this.includeSnapshots = includeSnapshots; return this; } + /** + * Gets the value of a flag indicating whether to include uncommitted blobs with a response set in this + * {@link ListBlobsOptions} instance. + * + * @return + * A value of <code>true</code> to include uncommitted blobs with a response, otherwise, <code>false</code>. + */ public boolean isIncludeUncommittedBlobs() { return includeUncommittedBlobs; } + /** + * Sets the value of an optional flag indicating whether to include uncommitted blobs with a response. Uncommitted + * blobs are blobs for which blocks have been uploaded, but which have not been committed with a request to + * {@link BlobContract#commitBlobBlocks(String, String, BlockList)} or + * {@link BlobContract#commitBlobBlocks(String, String, BlockList, CommitBlobBlocksOptions)}. + * + * @param includeUncommittedBlobs + * Set a value of <code>true</code> to include uncommitted blobs with a response, otherwise, + * <code>false</code>. + * @return + * A reference to this {@link ListBlobsOptions} instance. + */ public ListBlobsOptions setIncludeUncommittedBlobs(boolean includeUncommittedBlobs) { this.includeUncommittedBlobs = includeUncommittedBlobs; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java index 35acf17541c56..b36ff0552b2d5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -68,6 +68,18 @@ public List<BlobPrefixEntry> getBlobPrefixes() { return this.blobPrefixes; } + /** + * Gets the list of blobs that satisfied the request from the response. This list may contain only a portion of the + * blobs that satisfy the request, limited by a server timeout or a maximum results parameter. If there are more + * blobs that could satisfy the result, the server returns a <strong>NextMarker</strong> element with the response. + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get this value and pass it as a marker + * option to a subsequent list blobs request to get the next set of blob results. + * <p> + * Blobs are listed in alphabetical order in the response body, with upper-case letters listed first. + * + * @return + * A {@link List} of {@link BlobEntry} instances for the blobs that satisfied the request. + */ public List<BlobEntry> getBlobs() { return this.blobs; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java index 4d795d2410c08..2d9bdcca3e1d7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java @@ -2,61 +2,173 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a {@link BlobContract#listContainers(ListContainersOptions) listContainers} + * request. These options include a server response timeout for the request, a container name prefix filter, a marker + * for continuing requests, the maximum number of results to return in a request, and whether to include container + * metadata in the results. Options that are not set will not be passed to the server with a request. + */ public class ListContainersOptions extends BlobServiceOptions { private String prefix; private String marker; private int maxResults; private boolean includeMetadata; + /** + * Sets the optional server request timeout value associated with this {@link ListContainersOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link ListContainersOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link ListContainersOptions} instance. + */ @Override public ListContainersOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the container name prefix filter value set in this {@link ListContainersOptions} instance. + * + * @return + * A {@link String} containing the container name prefix value, if any. + */ public String getPrefix() { return prefix; } + /** + * Sets the optional container name prefix filter value to use in a request. If this value is set, the server will + * return only container names that match the prefix value in the response. + * <p> + * The <em>prefix</em> value only affects calls made on methods where this {@link ListContainersOptions} instance is + * passed as a parameter. + * + * @param prefix + * A {@link String} containing the container name prefix value to use to filter the request results. + * @return + * A reference to this {@link ListContainersOptions} instance. + */ public ListContainersOptions setPrefix(String prefix) { this.prefix = prefix; return this; } + /** + * Gets the marker value set in this {@link ListContainersOptions} instance. + * + * @return + * A {@link String} containing the marker value to use to specify the beginning of the request results. + */ public String getMarker() { return marker; } + /** + * Sets the optional marker value to use in a request. If this value is set, the server will return container names + * beginning at the specified marker in the response. + * <p> + * The List Containers operation returns a marker value in a <strong>NextMarker</strong> element if the container + * list returned is not complete. The marker value may then be used in a subsequent call to request the next set of + * container list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method on a {@link ListContainersResult} + * instance to get the marker value to set on a {@link ListContainersOptions} instance using a call to this method. + * Pass the {@link ListContainersOptions} instance as a parameter to a + * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * <p> + * The <em>marker</em> value only affects calls made on methods where this {@link ListContainersOptions} instance is + * passed as a parameter. + * + * @param marker + * A {@link String} containing the marker value to use to specify the beginning of the request results. + * @return + * A reference to this {@link ListContainersOptions} instance. + */ public ListContainersOptions setMarker(String marker) { this.marker = marker; return this; } + /** + * Gets the value of the maximum number of results to return set in this {@link ListContainersOptions} instance. + * + * @return + * The maximum number of results to return. + */ public int getMaxResults() { return maxResults; } + /** + * Sets the optional maximum number of results to return for a request. If this value is set, the server will return + * up to this number of results in the response. If a value is not specified, or a value greater than 5,000 is + * specified, the server will return up to 5,000 items. + * <p> + * If there are more containers than this value that can be returned, the server returns a marker value in a + * <strong>NextMarker</strong> element in the response. The marker value may then be used in a subsequent call to + * request the next set of container list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method on a {@link ListContainersResult} + * instance to get the marker value to set on a {@link ListContainersOptions} instance using a call to + * {@link ListContainersOptions#setMarker(String)}. Pass the {@link ListContainersOptions} instance as a parameter + * to a {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container + * list. + * <p> + * The <em>maxResults</em> value only affects calls made on methods where this {@link ListContainersOptions} + * instance is passed as a parameter. + * + * @param maxResults + * The maximum number of results to return. + * @return + * A reference to this {@link ListContainersOptions} instance. + */ public ListContainersOptions setMaxResults(int maxResults) { this.maxResults = maxResults; return this; } + /** + * Gets the value of a flag set in this {@link ListContainersOptions} instance indicating whether to include + * container metadata in the response to the request. + * + * @return + * <code>true</code> to include container metadata in the response to the request. + */ public boolean isIncludeMetadata() { return includeMetadata; } + /** + * Sets the value of an optional flag indicating whether to include container metadata with the response to the + * request. + * <p> + * The <em>includeMetadata</em> value only affects calls made on methods where this {@link ListContainersOptions} + * instance is passed as a parameter. + * + * @param includeMetadata + * Set to <code>true</code> to include container metadata in the response to the request. + * @return + * A reference to this {@link ListContainersOptions} instance. + */ public ListContainersOptions setIncludeMetadata(boolean includeMetadata) { this.includeMetadata = includeMetadata; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java index 36ca841ed734d..eb47ce77be2b2 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -24,9 +24,18 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import com.microsoft.windowsazure.services.blob.BlobContract; import com.microsoft.windowsazure.services.blob.implementation.MetadataAdapter; import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateAdapter; +/** + * A wrapper class for the response returned from a Blob Service REST API List Containers operation. This is returned by + * calls to implementations of {@link BlobContract#listContainers} and + * {@link BlobContract#listContainers(listContainersOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179352.aspx">List Containers</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ @XmlRootElement(name = "EnumerationResults") public class ListContainersResult { private List<Container> containers; @@ -36,124 +45,367 @@ public class ListContainersResult { private String nextMarker; private int maxResults; + /** + * Gets the container list returned in the response. + * + * @return + * A {@link List} of {@link Container} instances representing the blob containers returned by the request. + */ @XmlElementWrapper(name = "Containers") @XmlElement(name = "Container") public List<Container> getContainers() { return containers; } + /** + * Reserved for internal use. Sets the container list from the <strong>Containers</strong> element returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param value + * A {@link List} of {@link Container} instances representing the blob containers returned by the + * request. + */ public void setContainers(List<Container> value) { this.containers = value; } + /** + * Gets the base URI for invoking Blob Service REST API operations on the storage account. + * + * @return + * A {@link String} containing the base URI for Blob Service REST API operations on the storage account. + */ @XmlAttribute(name = "AccountName") public String getAccountName() { return accountName; } + /** + * Reserved for internal use. Sets the base URI for invoking Blob Service REST API operations on the storage account + * from the value of the <strong>AccountName</strong> attribute of the <strong>EnumerationResults</strong> element + * returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param accountName + * A {@link String} containing the base URI for Blob Service REST API operations on the storage account. + */ public void setAccountName(String accountName) { this.accountName = accountName; } + /** + * Gets the value of the filter used to return only containers beginning with the prefix. This value is not + * set if a prefix was not specified in the list containers request. + * + * @return + * A {@link String} containing the prefix used to filter the container names returned, if any. + */ @XmlElement(name = "Prefix") public String getPrefix() { return prefix; } + /** + * Reserved for internal use. Sets the filter used to return only containers beginning with the prefix from the + * <strong>Prefix</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param prefix + * A {@link String} containing the prefix used to filter the container names returned, if any. + */ public void setPrefix(String prefix) { this.prefix = prefix; } + /** + * Gets the value of the marker that was used to specify the beginning of the container list to return with the + * request. This value is not set if a marker was not specified in the request. + * <p> + * The List Containers operation returns a marker value in a <strong>NextMarker</strong> element if the container + * list returned is not complete. The marker value may then be used in a subsequent call to request the next set of + * container list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListContainersOptions} instance using a call to {@link ListContainersOptions#setMarker(String)}. Pass the + * {@link ListContainersOptions} instance as a parameter to a + * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * + * @return + * A {@link String} containing the marker used to specify the beginning of the container list returned, if + * any. + */ @XmlElement(name = "Marker") public String getMarker() { return marker; } + /** + * Reserved for internal use. Sets the marker used to specify the beginning of the container list to return from the + * <strong>Marker</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param marker + * A {@link String} containing the marker used to specify the beginning of the container list returned. + */ public void setMarker(String marker) { this.marker = marker; } + /** + * Gets the next marker value needed to specify the beginning of the next portion of the container list to return, + * if any was set in the response from the server. + * <p> + * The List Containers operation returns a marker value in a <strong>NextMarker</strong> element if the container + * list returned is not complete. The marker value may then be used in a subsequent call to request the next set of + * container list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListContainersOptions} instance using a call to {@link ListContainersOptions#setMarker(String)}. Pass the + * {@link ListContainersOptions} instance as a parameter to a + * {@link BlobContract#listContainers(ListContainersOptions)} call to get the next portion of the container list. + * + * @return + * A {@link String} containing the next marker value needed to specify the beginning of the next portion + * of the container list to return, if any was set in the response from the server. + */ @XmlElement(name = "NextMarker") public String getNextMarker() { return nextMarker; } + /** + * Reserved for internal use. Sets the next marker value needed to specify the beginning of the next portion of the + * container list to return from the <strong>NextMarker</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param nextMarker + * A {@link String} containing the next marker value needed to specify the beginning of the next portion + * of the container list to return. + */ public void setNextMarker(String nextMarker) { this.nextMarker = nextMarker; } + /** + * Gets the maximum number of container list items to return that was specified in the request. The number of + * containers returned in a single response will not exceed this value. This value is not set if a maximum number + * was not specified in the request. If there are more containers that satisfy the request than this maximum value, + * the server will include a next marker value in the response, which can be used to get the remaining containers + * with a subsequent request. + * <p> + * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListContainersOptions} instance using a call to {@link ListContainersOptions#setMarker(String)}. Pass the + * {@link ListContainersOptions} instance as a parameter to a + * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * + * @return + * The maximum number of container list items to return that was specified in the request, if any. + */ @XmlElement(name = "MaxResults") public int getMaxResults() { return maxResults; } + /** + * Reserved for internal use. Sets the maximum number of container list items to return that was specified in the + * request from the <strong>MaxResults</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param maxResults + * The maximum number of container list items to return that was specified in the request, if any. + */ public void setMaxResults(int maxResults) { this.maxResults = maxResults; } + /** + * Represents a container for blob storage returned by the server. A {@link Container} instance contains a copy of + * the container properties and metadata in the storage service as of the time the container list was requested. + */ public static class Container { private String name; private String url; private HashMap<String, String> metadata = new HashMap<String, String>(); private ContainerProperties properties; + /** + * Gets the name of the container. + * + * @return + * A {@link String} containing the name of the container. + */ @XmlElement(name = "Name") public String getName() { return name; } + /** + * Reserved for internal use. Sets the name of the container from the <strong>Name</strong> element returned in + * the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param name + * A {@link String} containing the name of the container. + */ public void setName(String name) { this.name = name; } + /** + * Gets the URI of the container. + * + * @return + * A {@link String} containing the URI of the container. + */ @XmlElement(name = "Url") public String getUrl() { return url; } + /** + * Reserved for internal use. Sets the URI of the container from the <strong>Url</strong> element returned in + * the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param url + * A {@link String} containing the URI of the container. + */ public void setUrl(String url) { this.url = url; } + /** + * Gets the container properties. The container properties include the last modified time and an Etag value. + * + * @return + * A {@link ContainerProperties} instance containing the properties associated with the container. + */ @XmlElement(name = "Properties") public ContainerProperties getProperties() { return properties; } + /** + * Reserved for internal use. Sets the container properties from the <strong>Properties</strong> element + * returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param properties + * A {@link ContainerProperties} instance containing the properties associated with the container. + */ public void setProperties(ContainerProperties properties) { this.properties = properties; } + /** + * Gets the container metadata as a map of name and value pairs. The container metadata is for client use and is + * opaque to the server. + * + * @return + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * the container metadata. + */ @XmlElement(name = "Metadata") @XmlJavaTypeAdapter(MetadataAdapter.class) public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the container metadata from the <strong>Metadata</strong> element + * returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param metadata + * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * of the container metadata. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } } + /** + * Represents the properties of a container for blob storage returned by the server. A {@link ContainerProperties} + * instance contains a copy of the container properties in the storage service as of the time the container list was + * requested. + */ public static class ContainerProperties { private Date lastModified; private String etag; + /** + * Gets the last modifed time of the container. This value can be used when updating or deleting a container + * using an optimistic concurrency model to prevent the client from modifying data that has been changed by + * another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the container. + */ @XmlElement(name = "Last-Modified") @XmlJavaTypeAdapter(RFC1123DateAdapter.class) public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the container from the + * <strong>Last-Modified</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the container. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the Etag of the container. This value can be used when updating or deleting a container using an + * optimistic concurrency model to prevent the client from modifying data that has been changed by another + * client. + * + * @return + * A {@link String} containing the server-assigned Etag value for the container. + */ @XmlElement(name = "Etag") public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the Etag of the container from the <strong>Etag</strong> element returned in + * the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param etag + * A {@link String} containing the server-assigned Etag value for the container. + */ public void setEtag(String etag) { this.etag = etag; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java index afef6edd6096b..1af40b003e052 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java @@ -2,56 +2,132 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import javax.xml.bind.annotation.XmlElement; +/** + * Represents the range of bytes in a single page within a page blob. + * <p> + * For a page update operation, the page range can be up to 4 MB in size. For a page clear operation, the page range can + * be up to the value of the blob's full size. + * <p> + * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of 512 + * and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + */ public class PageRange { private long start; private long end; + /** + * Default constructor. The start and end values must be set for this {@link PageRange} instance to be valid. + */ public PageRange() { } + /** + * Creates a page range from the specified start and end byte offsets, inclusive. + * <p> + * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of + * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * + * @param start + * The beginning offset value in bytes for the page range, inclusive. + * @param end + * The ending offset value in bytes for the page range, inclusive. + */ public PageRange(long start, long end) { this.start = start; this.end = end; } + /** + * Gets the byte offset of the start of the page range within the blob, inclusive. + * + * @return + * The beginning offset value in bytes for the page range, inclusive. + */ @XmlElement(name = "Start") public long getStart() { return start; } + /** + * Sets the byte offset of the start of the page range within the blob, inclusive. + * <p> + * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of + * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * + * @param start + * The beginning offset value in bytes for the page range, inclusive. + * @return + * A reference to this {@link PageRange} instance. + */ public PageRange setStart(long start) { this.start = start; return this; } + /** + * Gets the byte offset of the end of the page range within the blob, inclusive. + * + * @return + * The ending offset value in bytes for the page range, inclusive. + */ @XmlElement(name = "End") public long getEnd() { return end; } + /** + * Sets the byte offset of the end of the page range within the blob, inclusive. + * <p> + * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of + * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * + * @param end + * The ending offset value in bytes for the page range, inclusive. + * @return + * A reference to this {@link PageRange} instance. + */ public PageRange setEnd(long end) { this.end = end; return this; } + /** + * Gets the size of the page range in bytes. + * + * @return + * The size of the page range in bytes. + */ public long getLength() { return end - start + 1; } + /** + * Sets the length of the page range in bytes. This updates the byte offset of the end of the page range to the + * start value plus the length specified by the <em>value</em> parameter. The length must be a positive multiple of + * 512 bytes. + * <p> + * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of + * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * + * @param value + * The ending offset value in bytes for the page range, inclusive. + * @return + * A reference to this {@link PageRange} instance. + */ public PageRange setLength(long value) { this.end = this.start + value - 1; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataOptions.java index c30ec2b0d1f04..8c15e4c5c5ef3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataOptions.java @@ -2,41 +2,97 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#setBlobMetadata(String, String, java.util.HashMap, SetBlobMetadataOptions) setBlobMetadata} + * request. These options include an optional server timeout for the operation, a blob lease ID, and any access + * conditions for the operation. + */ public class SetBlobMetadataOptions extends BlobServiceOptions { private String leaseId; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link SetBlobMetadataOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link SetBlobMetadataOptions} instance + * is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link SetBlobMetadataOptions} instance. + */ @Override public SetBlobMetadataOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the lease ID to match for the blob set in this {@link SetBlobMetadataOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when setting metadata of the blob. If set, the lease must be active + * and the value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobMetadataOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link SetBlobMetadataOptions} instance. + */ public SetBlobMetadataOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the access conditions set in this {@link SetBlobMetadataOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for setting the metadata of a blob. By default, the set blob metadata operation + * will set the metadata unconditionally. Use this method to specify conditions on the ETag or last modified time + * value for performing the operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this {@link SetBlobMetadataOptions} + * instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link SetBlobMetadataOptions} instance. + */ public SetBlobMetadataOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesOptions.java index ed1e9c631cd7d..2dabb15421bc3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesOptions.java @@ -2,18 +2,28 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; +import com.microsoft.windowsazure.services.core.ServiceException; + +/** + * Represents the options that may be set on a + * {@link BlobContract#setBlobProperties(String, String, SetBlobPropertiesOptions) setBlobProperties} request. + * These options include an optional server timeout for the operation, the MIME content type and content encoding for + * the blob, the content length, the content language, the MD5 hash, a cache control value, a blob lease ID, a sequence + * number and sequence number action value, and any access conditions for the operation. + */ public class SetBlobPropertiesOptions extends BlobServiceOptions { private String leaseId; private String contentType; @@ -26,97 +36,322 @@ public class SetBlobPropertiesOptions extends BlobServiceOptions { private Long sequenceNumber; private AccessCondition accessCondition; + /** + * Sets the optional server request timeout value associated with this {@link SetBlobPropertiesOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link SetBlobPropertiesOptions} + * instance is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ @Override public SetBlobPropertiesOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the MIME content type value set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the MIME content type value set, if any. + */ public String getContentType() { return contentType; } + /** + * Sets the optional MIME content type for the blob content. This value will be returned to clients in the + * <code>Content-Type</code> header of the response when the blob data or blob properties are requested. If no + * content type is specified, the default content type is <strong>application/octet-stream</strong>. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param contentType + * A {@link String} containing the MIME content type value to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setContentType(String contentType) { this.contentType = contentType; return this; } + /** + * Gets the new page blob size set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * The new size to set for a page blob. + */ public Long getContentLength() { return contentLength; } + /** + * Sets the size of a page blob to the specified size. If the specified <em>contentLength</em> value is less than + * the current size of the blob, then all pages above the specified value are cleared. + * <p> + * This property cannot be used to change the size of a block blob. Setting this property for a block blob causes a + * {@link ServiceException} to be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param contentLength + * The new size to set for a page blob. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setContentLength(Long contentLength) { this.contentLength = contentLength; return this; } + /** + * Gets the HTTP content encoding value set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the HTTP content encoding value set, if any. + */ public String getContentEncoding() { return contentEncoding; } + /** + * Sets the optional HTML content encoding value for the blob content. Use this value to specify any HTTP content + * encodings applied to the blob, passed as a <code>x-ms-blob-content-encoding</code> header value to the server. + * This value will be returned to clients in the headers of the response when the blob data or blob properties are + * requested. Pass an empty value to update a blob to the default value, which will cause no content encoding header + * to be returned with the blob. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param contentEncoding + * A {@link String} containing the <code>x-ms-blob-content-encoding</code> header value to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setContentEncoding(String contentEncoding) { this.contentEncoding = contentEncoding; return this; } + /** + * Gets the HTTP content language header value set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the HTTP content language header value set, if any. + */ public String getContentLanguage() { return contentLanguage; } + /** + * Sets the optional HTTP content language header value for the blob content. Use this value to + * specify the content language of the blob. This value will be returned to clients in the + * <code>x-ms-blob-content-language</code> header of the response when the blob data or blob properties are + * requested. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param contentLanguage + * A {@link String} containing the <code>x-ms-blob-content-language</code> header value to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setContentLanguage(String contentLanguage) { this.contentLanguage = contentLanguage; return this; } + /** + * Gets the MD5 hash value for the blob content set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the MD5 hash value for the blob content set, if any. + */ public String getContentMD5() { return contentMD5; } + /** + * Sets the optional MD5 hash value for the blob content. This value will be returned to clients in the + * <code>x-ms-blob-content-md5</code> header value of the response when the blob data or blob properties are + * requested. This hash is used to verify the integrity of the blob during transport. When this header is specified, + * the storage service checks the hash of the content that has arrived with the one that was sent. If the two hashes + * do not match, the operation will fail with error code 400 (Bad Request), which will cause a ServiceException to + * be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param contentMD5 + * A {@link String} containing the MD5 hash value for the blob content to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; return this; } + /** + * Gets the HTTP cache control value set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the HTTP cache control value set, if any. + */ public String getCacheControl() { return cacheControl; } + /** + * Sets the optional HTTP cache control value for the blob content. The Blob service stores + * this value but does not use or modify it. This value will be returned to clients in the headers of the response + * when the blob data or blob properties are requested. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param cacheControl + * A {@link String} containing the HTTP cache control value to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setCacheControl(String cacheControl) { this.cacheControl = cacheControl; return this; } + /** + * Gets the sequence number value set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * The sequence number to set, if any. + */ public Long getSequenceNumber() { return sequenceNumber; } + /** + * Sets the page blob sequence number. The sequence number is a user-controlled property that you can use to track + * requests and manage concurrency issues. This value is optional, but is required if the sequence number action + * value is set to <code>max</code> or <code>update</code>. + * <p> + * Use the <em>sequenceNumber</em> parameter together with the sequence number action to update the blob's sequence + * number, either to the specified value or to the higher of the values specified with the request or currently + * stored with the blob. This header should not be specified if the sequence number action is set to + * <code>increment</code>; in this case the service automatically increments the sequence number by one. + * <p> + * To set the sequence number to a value of your choosing, this property must be specified on the request together + * with a sequence number action value of <code>update</code>. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param sequenceNumber + * The sequence number to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setSequenceNumber(Long sequenceNumber) { this.sequenceNumber = sequenceNumber; return this; } + /** + * Gets the lease ID to match for the blob set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when setting properties of the blob. If set, the lease must be active + * and the value must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the sequence number action set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * A {@link String} containing the sequence number action set, if any. + */ public String getSequenceNumberAction() { return sequenceNumberAction; } + /** + * Sets an optional sequence number action for a page blob. This value is required if a sequence number is set for + * the request. + * <p> + * The <em>sequenceNumberAction</em> parameter indicates how the service should modify the page blob's sequence + * number. Specify one of the following strings for this parameter: + * <ul> + * <li><code>max</code> - Sets the sequence number to be the higher of the value included with the request and the + * value currently stored for the blob.</li> + * <li><code>update</code> - Sets the sequence number to the value included with the request.</li> + * <li><code>increment</code> - Increments the value of the sequence number by 1. If specifying this option, do not + * set the sequence number value; doing so will cause a {@link ServiceException} to be thrown.</li> + * </ul> + * <p> + * Note that this value only affects calls made on methods where this {@link SetBlobPropertiesOptions} instance is + * passed as a parameter. + * + * @param sequenceNumberAction + * A {@link String} containing the sequence number action to set on the page blob. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setSequenceNumberAction(String sequenceNumberAction) { this.sequenceNumberAction = sequenceNumberAction; return this; } + /** + * Gets the access conditions set in this {@link SetBlobPropertiesOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions for setting the properties of a blob. By default, the set blob properties operation + * will set the properties unconditionally. Use this method to specify conditions on the ETag or last modified time + * value for performing the operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this {@link SetBlobPropertiesOptions} + * instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link SetBlobPropertiesOptions} instance. + */ public SetBlobPropertiesOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesResult.java index f505058f1890c..599a9576b9eed 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobPropertiesResult.java @@ -2,45 +2,112 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Set Blob Properties operation. This is + * returned by calls to implementations of + * {@link BlobContract#setBlobProperties(String, String, SetBlobPropertiesOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/ee691966.aspx">Set Blob Properties</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class SetBlobPropertiesResult { private String etag; private Date lastModified; private Long sequenceNumber; + /** + * Gets the ETag of the blob. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> element returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time of the blob. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the page blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } + /** + * Gets the sequence number of the page blob. This value can be used when updating or deleting a page blob using an + * optimistic concurrency model to prevent the client from modifying data that has been changed by another + * client. + * + * @return + * A {@link String} containing the client-assigned sequence number value for the page blob. + */ public Long getSequenceNumber() { return sequenceNumber; } + /** + * Reserved for internal use. Sets the sequence number of the page blob from the + * <strong>x-ms-blob-sequence-number</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param sequenceNumber + * A {@link String} containing the client-assigned sequence number value for the page blob. + */ public void setSequenceNumber(Long sequenceNumber) { this.sequenceNumber = sequenceNumber; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetContainerMetadataOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetContainerMetadataOptions.java index f27304a09b755..0830b04e3d049 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetContainerMetadataOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetContainerMetadataOptions.java @@ -2,31 +2,73 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#setContainerMetadata(String, java.util.HashMap, SetContainerMetadataOptions)} request. These + * options include a server response timeout for the request and access conditions that specify whether to perform the + * operation or not depending on the values of the Etag or last modified time of the container. Options that are not set + * will not be passed to the server with a request. + */ public class SetContainerMetadataOptions extends BlobServiceOptions { private AccessCondition accessCondition; + /** + * Sets the server request timeout value associated with this {@link SetContainerMetadataOptions} instance. + * <p> + * The <em>timeout</em> value only affects calls made on methods where this {@link SetContainerMetadataOptions} + * instance is passed as a parameter. + * + * @param timeout + * The server request timeout value to set in milliseconds. + * @return + * A reference to this {@link SetContainerMetadataOptions} instance. + */ @Override public SetContainerMetadataOptions setTimeout(Integer timeout) { super.setTimeout(timeout); return this; } + /** + * Gets the access conditions associated with this {@link SetContainerMetadataOptions} instance. + * + * @return + * An {@link AccessCondition} reference containing the Etag and last modified time conditions for performing + * the set container metadata operation, or <code>null</code> if not set. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets the access conditions associated with this {@link SetContainerMetadataOptions} instance. By default, the set + * container metadata operation will set the container metadata unconditionally. Use this method to specify + * conditions on the Etag or last modified time value for performing the set container metadata operation. + * <p> + * The <em>accessCondition</em> value only affects calls made on methods where this + * {@link SetContainerMetadataOptions} instance is passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} reference containing the Etag and last modified time conditions for + * performing the set container metadata operation. Specify <code>null</code> to make the operation + * unconditional. + * @return + * A reference to this {@link SetContainerMetadataOptions} instance. + */ public SetContainerMetadataOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; From e913b38c187c404f98eba1ee8efec995e02041dd Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Mon, 30 Apr 2012 16:58:00 -0700 Subject: [PATCH 67/76] Add remaining files with Javadocs comments. --- .../services/blob/models/AccessCondition.java | 65 ++-- .../blob/models/CreateBlobPagesOptions.java | 64 ++++ .../blob/models/GetBlobMetadataResult.java | 4 +- .../services/blob/models/ListBlobsResult.java | 313 +++++++++++++++++- .../blob/models/ServiceProperties.java | 215 +++++++++++- .../blob/models/SetBlobMetadataResult.java | 65 +++- 6 files changed, 687 insertions(+), 39 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessCondition.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessCondition.java index 24018221dc132..90791bacfeb21 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessCondition.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessCondition.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -19,18 +19,18 @@ import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter; /** - * Represents a set of access conditions to be used for operations against the storage services. + * Represents a set of access conditions for operations that use storage services. */ public final class AccessCondition { /** - * Specifies that no access condition is set. + * Specifies an access condition with no conditions set. */ public static final AccessCondition NONE = new AccessCondition(AccessConditionHeaderType.NONE, null); /** - * Returns an access condition such that an operation will be performed only if the resource's ETag value matches - * the specified ETag value. + * Creates an access condition that only allows an operation if the resource's ETag value matches the specified ETag + * value. * <p> * Setting this access condition modifies the request to include the HTTP <i>If-Match</i> conditional header. If * this access condition is set, the operation is performed only if the ETag of the resource matches the specified @@ -49,8 +49,8 @@ public static AccessCondition ifMatch(String etag) { } /** - * Returns an access condition such that an operation will be performed only if the resource has been modified since - * the specified time. + * Creates an access condition that only allows an operation if the resource has been modified since the specified + * time. * <p> * Setting this access condition modifies the request to include the HTTP <i>If-Modified-Since</i> conditional * header. If this access condition is set, the operation is performed only if the resource has been modified since @@ -70,8 +70,8 @@ public static AccessCondition ifModifiedSince(Date lastMotified) { } /** - * Returns an access condition such that an operation will be performed only if the resource's ETag value does not - * match the specified ETag value. + * Creates an access condition that only allows an operation if the resource's ETag value does not match the + * specified ETag value. * <p> * Setting this access condition modifies the request to include the HTTP <i>If-None-Match</i> conditional header. * If this access condition is set, the operation is performed only if the ETag of the resource does not match the @@ -90,8 +90,8 @@ public static AccessCondition ifNoneMatch(String etag) { } /** - * Returns an access condition such that an operation will be performed only if the resource has not been modified - * since the specified time. + * Creates an access condition that only allows an operation if the resource has not been modified since the + * specified time. * <p> * Setting this access condition modifies the request to include the HTTP <i>If-Unmodified-Since</i> conditional * header. If this access condition is set, the operation is performed only if the resource has not been modified @@ -111,12 +111,12 @@ public static AccessCondition ifNotModifiedSince(Date lastMotified) { } /** - * Represents the header type. + * Represents the access condition header type. */ private AccessConditionHeaderType header = AccessConditionHeaderType.NONE; /** - * Represents the header value. + * Represents the access condition header value. */ private String value; @@ -140,18 +140,43 @@ protected AccessCondition(AccessConditionHeaderType headerType, String value) { this.setValue(value); } + /** + * Gets the access condition header type set in this <code>AccessCondition</code> instance. + * + * @return + * The {@link AccessConditionHeaderType} set in this <code>AccessCondition</code> instance. + */ public AccessConditionHeaderType getHeader() { return header; } + /** + * Sets the access condition header type in this <code>AccessCondition</code> instance. + * + * @param header + * The {@link AccessConditionHeaderType} to set in this <code>AccessCondition</code> instance. + */ public void setHeader(AccessConditionHeaderType header) { this.header = header; } + /** + * Gets the access condition value set in this <code>AccessCondition</code> instance. + * + * @return + * A {@link String} containing the access condition value set in this <code>AccessCondition</code> instance. + */ public String getValue() { return value; } + /** + * Sets the access condition value in this <code>AccessCondition</code> instance. + * + * @param value + * A {@link String} containing the access condition value to set in this <code>AccessCondition</code> + * instance. + */ public void setValue(String value) { this.value = value; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java index dba32e8f767ff..e87466dd35c0c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobPagesOptions.java @@ -14,6 +14,14 @@ */ package com.microsoft.windowsazure.services.blob.models; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the options that may be set on a + * {@link BlobContract#createBlobPages(String, String, PageRange, long, java.io.InputStream, CreateBlobPagesOptions)} + * request. These options include an optional server timeout for the operation, a blob lease ID to create pages in a + * blob with an active lease, an optional MD5 hash for the content, and any access conditions to satisfy. + */ public class CreateBlobPagesOptions extends BlobServiceOptions { private String leaseId; private String contentMD5; @@ -36,28 +44,84 @@ public CreateBlobPagesOptions setTimeout(Integer timeout) { return this; } + /** + * Gets the lease ID to match for the blob set in this {@link CreateBlobPagesOptions} instance. + * + * @return + * A {@link String} containing the lease ID set, if any. + */ public String getLeaseId() { return leaseId; } + /** + * Sets an optional lease ID value to match when getting the blob. If set, the lease must be active and the value + * must match the lease ID set on the leased blob for the operation to succeed. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobPagesOptions} instance is + * passed as a parameter. + * + * @param leaseId + * A {@link String} containing the lease ID to set. + * @return + * A reference to this {@link CreateBlobPagesOptions} instance. + */ public CreateBlobPagesOptions setLeaseId(String leaseId) { this.leaseId = leaseId; return this; } + /** + * Gets the MD5 hash value for the page content set in this {@link CreateBlobPagesOptions} instance. + * + * @return + * A {@link String} containing the MD5 hash value for the block content set, if any. + */ public String getContentMD5() { return contentMD5; } + /** + * Sets the optional MD5 hash value for the page content. This hash is used to verify the integrity of the blob + * during transport. When this value is specified, the storage service checks the hash of the content that has + * arrived with the one that was sent. If the two hashes do not match, the operation will fail with error code 400 + * (Bad Request), which will cause a ServiceException to be thrown. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobPagesOptions} instance is + * passed as a parameter. + * + * @param contentMD5 + * A {@link String} containing the MD5 hash value for the block content to set. + * @return + * A reference to this {@link CreateBlobPagesOptions} instance. + */ public CreateBlobPagesOptions setContentMD5(String contentMD5) { this.contentMD5 = contentMD5; return this; } + /** + * Gets the access conditions set in this {@link CreateBlobPagesOptions} instance. + * + * @return + * An {@link AccessCondition} containing the access conditions set, if any. + */ public AccessCondition getAccessCondition() { return accessCondition; } + /** + * Sets optional access conditions for getting the blob. The operation will return an error if the access conditions + * are not met. + * <p> + * Note that this value only affects calls made on methods where this {@link CreateBlobPagesOptions} instance is + * passed as a parameter. + * + * @param accessCondition + * An {@link AccessCondition} containing the access conditions to set. + * @return + * A reference to this {@link CreateBlobPagesOptions} instance. + */ public CreateBlobPagesOptions setAccessCondition(AccessCondition accessCondition) { this.accessCondition = accessCondition; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java index a31af10c3dc9b..bba525e3db1e3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java @@ -93,7 +93,7 @@ public void setLastModified(Date lastModified) { * opaque to the server. * * @return - * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values of + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values of * the blob metadata. */ public HashMap<String, String> getMetadata() { @@ -108,7 +108,7 @@ public HashMap<String, String> getMetadata() { * the server. * * @param metadata - * A {@link java.util.HashMap} of key-value pairs of {@link String} containing the names and values + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values * of the blob metadata. */ public void setMetadata(HashMap<String, String> metadata) { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java index b36ff0552b2d5..feb0bf40944e3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java @@ -26,8 +26,17 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import com.microsoft.windowsazure.services.blob.BlobContract; import com.microsoft.windowsazure.services.blob.implementation.MetadataAdapter; +/** + * A wrapper class for the response returned from a Blob Service REST API Get Blobs operation. This is returned by + * calls to implementations of {@link BlobContract#listBlobs(String)} and + * {@link BlobContract#listBlobs(String, ListBlobsOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135734.aspx">Get Blobs</a> documentation on + * MSDN for details of the underlying Blob Service REST API operation. + */ @XmlRootElement(name = "EnumerationResults") public class ListBlobsResult { private List<BlobPrefixEntry> blobPrefixes = new ArrayList<BlobPrefixEntry>(); @@ -39,6 +48,14 @@ public class ListBlobsResult { private String delimiter; private int maxResults; + /** + * Gets the list of <code>ListBlobsEntry</code> entries generated from the server response to the list blobs + * request. + * + * @return + * The {@link List} of {@link ListBlobsEntry} entries generated from the server response to the list blobs + * request. + */ @XmlElementWrapper(name = "Blobs") @XmlElementRefs({ @XmlElementRef(name = "BlobPrefix", type = BlobPrefixEntry.class), @XmlElementRef(name = "Blob", type = BlobEntry.class) }) @@ -49,6 +66,14 @@ public List<ListBlobsEntry> getEntries() { return result; } + /** + * Sets the lists of blob entries and blob prefix entries from a common list of <code>ListBlobsEntry</code> entries + * generated from the server response to the list blobs request. + * + * @param entries + * The {@link List} of {@link ListBlobsEntry} entries to set the lists of blob entries and blob prefix + * entries from. + */ public void setEntries(List<ListBlobsEntry> entries) { // Split collection into "blobs" and "blobPrefixes" collections this.blobPrefixes = new ArrayList<BlobPrefixEntry>(); @@ -64,6 +89,33 @@ else if (entry instanceof BlobEntry) { } } + /** + * Gets the list of blob prefix entries that satisfied the request. Each <code>BlobPrefixEntry</code> represents one + * or more blobs that have a common substring up to the delimiter specified in the request options. + * <p> + * This list may contain only a portion of the blob prefix entries that satisfy the request, limited by a server + * timeout or a maximum results parameter. If there are more blob entries and blob prefix entries that could satisfy + * the result, the server returns a <strong>NextMarker</strong> element with the response. + * <p> + * The delimiter option enables the caller to traverse the blob namespace by using a user-configured delimiter. In + * this way, you can traverse a virtual hierarchy of blobs as though it were a file system. The delimiter may be a + * single character or a string. When the request includes the delimiter option, the response includes a + * <code>BlobPrefixEntry</code> in place of all blobs whose names begin with the same substring up to the appearance + * of the delimiter string. The value of {@link BlobPrefixEntry#getName()} is <em>substring</em>+ <em>delimiter</em> + * , where <em>substring</em> is the common substring that begins one or more blob names, and <em>delimiter</em> is + * the value of the delimiter option. + * <p> + * To move down a level in the virtual hierarchy, you can use the {@link BlobPrefixEntry#getName()} method to get + * the prefix value to use to make a subsequent call to list blobs that begin with this prefix. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get this value and pass it as a marker + * option to a subsequent list blobs request to get the next set of blob results. + * <p> + * Blobs are listed in alphabetical order in the response body, with upper-case letters listed first. + * + * @return + * A {@link List} of {@link BlobEntry} instances for the blobs that satisfied the request. + */ public List<BlobPrefixEntry> getBlobPrefixes() { return this.blobPrefixes; } @@ -71,7 +123,9 @@ public List<BlobPrefixEntry> getBlobPrefixes() { /** * Gets the list of blobs that satisfied the request from the response. This list may contain only a portion of the * blobs that satisfy the request, limited by a server timeout or a maximum results parameter. If there are more - * blobs that could satisfy the result, the server returns a <strong>NextMarker</strong> element with the response. + * blobs or blob prefixes that could satisfy the result, the server returns a <strong>NextMarker</strong> element + * with the response. + * <p> * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get this value and pass it as a marker * option to a subsequent list blobs request to get the next set of blob results. * <p> @@ -84,78 +138,245 @@ public List<BlobEntry> getBlobs() { return this.blobs; } + /** + * Gets the value of the filter used to return only blobs beginning with the prefix. This value is not set if a + * prefix was not specified in the list blobs request. + * + * @return + * A {@link String} containing the prefix used to filter the blob names returned, if any. + */ @XmlElement(name = "Prefix") public String getPrefix() { return prefix; } + /** + * Reserved for internal use. Sets the filter used to return only blobs beginning with the prefix from the + * <strong>Prefix</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param prefix + * A {@link String} containing the prefix used to filter the blob names returned, if any. + */ public void setPrefix(String prefix) { this.prefix = prefix; } + /** + * Gets the value of the marker that was used to specify the beginning of the container list to return with the + * request. This value is not set if a marker was not specified in the request. + * <p> + * The list blobs operation returns a marker value in a <strong>NextMarker</strong> element if the blob list + * returned is not complete. The marker value may then be used in a subsequent call to request the next set of blob + * list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListBlobsOptions} instance using a call to {@link ListBlobsOptions#setMarker(String)}. Pass the + * {@link ListBlobsOptions} instance as a parameter to a {@link BlobContract#listBlobs(String, ListBlobsOptions) + * listBlobs} call to get the next portion of the blob list. + * + * @return + * A {@link String} containing the marker used to specify the beginning of the blob list returned, if any. + */ @XmlElement(name = "Marker") public String getMarker() { return marker; } + /** + * Reserved for internal use. Sets the marker used to specify the beginning of the blob list to return from the + * <strong>Marker</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param marker + * A {@link String} containing the marker used to specify the beginning of the blob list returned. + */ public void setMarker(String marker) { this.marker = marker; } + /** + * Gets the next marker value needed to specify the beginning of the next portion of the blob list to return, if any + * was set in the response from the server. + * <p> + * The list blobs operation returns a marker value in a <strong>NextMarker</strong> element if the blob list + * returned is not complete. The marker value may then be used in a subsequent call to request the next set of blob + * list items. The marker value is opaque to the client. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListBlobsOptions} instance using a call to {@link ListBlobsOptions#setMarker(String)}. Pass the + * {@link ListBlobsOptions} instance as a parameter to a {@link BlobContract#listBlobs(String, ListBlobsOptions) + * listBlobs} call to get the next portion of the blob list. + * + * @return + * A {@link String} containing the next marker value needed to specify the beginning of the next portion + * of the blob list to return, if any was set in the response from the server. + */ @XmlElement(name = "NextMarker") public String getNextMarker() { return nextMarker; } + /** + * Reserved for internal use. Sets the next marker value needed to specify the beginning of the next portion of the + * blob list to return from the <strong>NextMarker</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param nextMarker + * A {@link String} containing the next marker value needed to specify the beginning of the next portion + * of the blob list to return. + */ public void setNextMarker(String nextMarker) { this.nextMarker = nextMarker; } + /** + * Gets the maximum results to return used to generate the response, if present. The number of entries returned in + * the response will not exceed this number, including all <strong>BlobPrefix</strong> elements. This value is not + * set if a maximum number was not specified in the request. If the request does not specify this parameter or + * specifies a value greater than 5,000, the server will return up to 5,000 items. If there are more blobs or blob + * prefixes that satisfy the request than this maximum value, the server will include a next marker value in the + * response, which can be used to get the remaining blobs with a subsequent request. + * <p> + * Use the {@link ListBlobsResult#getNextMarker() getNextMarker} method to get the marker value to set on a + * {@link ListBlobsOptions} instance using a call to {@link ListBlobsOptions#setMarker(String)}. Pass the + * {@link ListBlobsOptions} instance as a parameter to a {@link BlobContract#listBlobs(String, ListBlobsOptions) + * listBlobs} call to get the next portion of the blob list. + * + * @return + * The maximum results to return value in the response, if any. + */ @XmlElement(name = "MaxResults") public int getMaxResults() { return maxResults; } + /** + * Reserved for internal use. Sets the maximum results to return value from the <strong>MaxResults</strong> element + * of the <strong>EnumerationResults</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param maxResults + * The maximum results to return value in the response, if any. + */ public void setMaxResults(int maxResults) { this.maxResults = maxResults; } + /** + * Gets the delimiter value used to generate the response, if present. When the request includes this parameter, the + * operation returns <strong>BlobPrefix</strong> elements in the response body that act as a placeholder for all + * blobs whose names begin with the same substring up to the appearance of the delimiter character. + * + * @return + * A {@link String} containing the delimiter value used as a request parameter. + */ @XmlElement(name = "Delimiter") public String getDelimiter() { return delimiter; } + /** + * Reserved for internal use. Sets the delimiter value from the <strong>Delimiter</strong> element of the + * <strong>EnumerationResults</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param delimiter + * A {@link String} containing the delimiter value in the response, if any. + */ public void setDelimiter(String delimiter) { this.delimiter = delimiter; } + /** + * Gets the container URI. This value is returned in the <strong>ContainerName</strong> attribute of the + * <strong>EnumerationResults</strong> element returned in the response. + * + * @return + * A {@link String} containing the container URI. + */ @XmlAttribute(name = "ContainerName") public String getContainerName() { return containerName; } + /** + * Reserved for internal use. Sets the container URI value from the <strong>ContainerName</strong> attribute of the + * <strong>EnumerationResults</strong> element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param containerName + * A {@link String} containing the container URI. + */ public void setContainerName(String containerName) { this.containerName = containerName; } + /** + * The abstract base class for <code>Blob</code> and <code>BlobPrefix</code> entries in the list of results returned + * in the response. + */ public static abstract class ListBlobsEntry { } + /** + * Represents a <strong>BlobPrefix</strong> element returned in the response. When the request includes a delimiter + * parameter, a single <strong>BlobPrefix</strong> element is returned in place of all blobs whose names begin with + * the same substring up to the appearance of the delimiter character. + */ @XmlRootElement(name = "BlobPrefix") public static class BlobPrefixEntry extends ListBlobsEntry { private String name; + /** + * Gets the value of the <strong>Name</strong> element within a <strong>BlobPrefix</strong> element returned in + * the response. The value of the element is <em>substring</em>+<em>delimiter</em>, where <em>substring</em> is + * the common substring that begins one or more blob names, and <em>delimiter</em> is the value of the delimiter + * parameter. + * + * @return + * A {@link String} containing the common substring that begins one or more blob names up to and + * including the delimiter specified in the request. + */ @XmlElement(name = "Name") public String getName() { return name; } + /** + * Reserved for internal use. Sets the blob prefix name from the <strong>Name</strong> element in the + * <strong>BlobPrefix</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param name + * A {@link String} containing the value of the <strong>Name</strong> element within a + * <strong>BlobPrefix</strong> element. + */ public void setName(String name) { this.name = name; } } + /** + * Represents a <strong>Blob</strong> element returned in the response. A <code>BlobEntry</code> includes + * committed blobs, and can optionally include blob metadata, blob snapshots, and uncommitted blobs, depending on + * the request options set. + */ @XmlRootElement(name = "Blob") public static class BlobEntry extends ListBlobsEntry { private String name; @@ -164,48 +385,138 @@ public static class BlobEntry extends ListBlobsEntry { private HashMap<String, String> metadata = new HashMap<String, String>(); private BlobProperties properties; + /** + * Gets the value of the <strong>Name</strong> element within a <strong>Blob</strong> element returned in + * the response. The value of the element is the complete name of the blob, including any virtual hierarchy. + * + * @return + * A {@link String} containing the complete blob name. + */ @XmlElement(name = "Name") public String getName() { return name; } + /** + * Reserved for internal use. Sets the blob name from the <strong>Name</strong> element in the + * <strong>Blob</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param name + * A {@link String} containing the value of the <strong>Name</strong> element within a + * <strong>Blob</strong> element. + */ public void setName(String name) { this.name = name; } + /** + * Gets the value of the <strong>Url</strong> element within a <strong>Blob</strong> element returned in + * the response. The value of the element is the complete URI address of the blob, including any virtual + * hierarchy. + * + * @return + * A {@link String} containing the complete URI address of the blob. + */ @XmlElement(name = "Url") public String getUrl() { return url; } + /** + * Reserved for internal use. Sets the blob URI address from the <strong>Uri</strong> element in the + * <strong>Blob</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param url + * A {@link String} containing the complete URI address of the blob. + */ public void setUrl(String url) { this.url = url; } + /** + * Gets a {@link BlobProperties} instance with the blob properties returned in the response. + * + * @return + * A {@link BlobProperties} instance with the blob properties returned in the response. + */ @XmlElement(name = "Properties") public BlobProperties getProperties() { return properties; } + /** + * Reserved for internal use. Sets the blob properties from the <strong>Uri</strong> element in the + * <strong>Blob</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param properties + * A {@link BlobProperties} instance with the blob properties returned in the response. + */ public void setProperties(BlobProperties properties) { this.properties = properties; } + /** + * Gets the snapshot timestamp value for a blob snapshot returned in the response. The value is set from the + * <strong>Snapshot</strong> element in the <strong>Blob</strong> element in the <strong>Blobs</strong> list in + * the response. Snapshots are included in the enumeration only if specified in the request options. Snapshots + * are listed from oldest to newest in the response. + * + * @return + * A {@link String} containing the snapshot timestamp of the blob snapshot. + */ @XmlElement(name = "Snapshot") public String getSnapshot() { return snapshot; } + /** + * Reserved for internal use. Sets the blob snapshot timestamp from the <strong>Snapshot</strong> element in the + * <strong>Blob</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param snapshot + * A {@link String} containing the snapshot timestamp of the blob snapshot. + */ public void setSnapshot(String snapshot) { this.snapshot = snapshot; } + /** + * Gets the blob's metadata collection set in the <strong>Metadata</strong> element in the <strong>Blob</strong> + * element in the <strong>Blobs</strong> list in the response. Metadata is included in the enumeration only if + * specified in the request options. + * + * @return + * A {@link HashMap} of name-value pairs of {@link String} containing the blob metadata set, if any. + */ @XmlElement(name = "Metadata") @XmlJavaTypeAdapter(MetadataAdapter.class) public HashMap<String, String> getMetadata() { return metadata; } + /** + * Reserved for internal use. Sets the blob metadata from the <strong>Metadata</strong> element in the + * <strong>Blob</strong> element in the <strong>Blobs</strong> list in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned + * by the server. + * + * @param metadata + * A {@link java.util.HashMap} of name-value pairs of {@link String} containing the names and values + * of the blob metadata, if present. + */ public void setMetadata(HashMap<String, String> metadata) { this.metadata = metadata; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ServiceProperties.java index 4171ef288f4a7..cdca5eee0e01b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ServiceProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ServiceProperties.java @@ -2,54 +2,108 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * Represents the Blob service properties that can be set on a storage account, including Windows Azure Storage + * Analytics. This class is used by the {@link BlobContract#getServiceProperties()} method to return the service + * property values set on the storage account, and by the {@link BlobContract#setServiceProperties(ServiceProperties)} + * and {@link BlobContract#setServiceProperties(ServiceProperties, BlobServiceOptions)} methods to set the values of the + * service properties. + */ @XmlRootElement(name = "StorageServiceProperties") public class ServiceProperties { private Logging logging = new Logging(); private Metrics metrics = new Metrics(); private String defaultServiceVersion; + /** + * Gets the value of the logging options on the storage account. + * + * @return + * A {@link Logging} instance containing the logging options. + */ @XmlElement(name = "Logging") public Logging getLogging() { return logging; } + /** + * Sets the value of the logging options on the storage account. + * + * @param logging + * A {@link Logging} instance containing the logging options. + */ public void setLogging(Logging logging) { this.logging = logging; } + /** + * Gets the value of the metrics options on the storage account. + * + * @return + * A {@link Metrics} instance containing the metrics options. + */ @XmlElement(name = "Metrics") public Metrics getMetrics() { return metrics; } + /** + * Sets the value of the metrics options on the storage account. + * + * @param metrics + * A {@link Metrics} instance containing the metrics options. + */ public void setMetrics(Metrics metrics) { this.metrics = metrics; } + /** + * Gets the default service version string used for operations on the storage account. + * + * @return + * A {@link String} containing the default service version string used for operations on the storage + * account. + */ @XmlElement(name = "DefaultServiceVersion") public String getDefaultServiceVersion() { return defaultServiceVersion; } + /** + * Sets the default service version string used for operations on the storage account. This value is optional + * for a set service properties request. Allowed values include version 2009-09-19 and more recent versions. + * <p> + * See <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894041.aspx">Storage Services Versioning</a> + * on MSDN for more information on applicable versions. + * + * @param defaultServiceVersion + * A {@link String} containing the default service version string used for operations on the storage + * account. + */ public void setDefaultServiceVersion(String defaultServiceVersion) { this.defaultServiceVersion = defaultServiceVersion; } + /** + * Represents the logging options that can be set on a storage account. + */ public static class Logging { private String version; private Boolean delete; @@ -57,113 +111,258 @@ public static class Logging { private Boolean write; private RetentionPolicy retentionPolicy; + /** + * Gets the retention policy for logging data set on the storage account. + * + * @return + * The {@link RetentionPolicy} set on the storage account. + */ @XmlElement(name = "RetentionPolicy") public RetentionPolicy getRetentionPolicy() { return retentionPolicy; } + /** + * Sets the retention policy to use for logging data on the storage account. + * + * @param retentionPolicy + * The {@link RetentionPolicy} to set on the storage account. + */ public void setRetentionPolicy(RetentionPolicy retentionPolicy) { this.retentionPolicy = retentionPolicy; } + /** + * Gets a flag indicating whether all write requests are logged. + * + * @return + * A flag value of <code>true</code> if all write operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Write") public boolean isWrite() { return write; } + /** + * Sets a flag indicating whether all write requests should be logged. + * + * @param write + * Set a flag value of <code>true</code> to log all write operations; otherwise, <code>false</code>. + */ public void setWrite(boolean write) { this.write = write; } + /** + * Gets a flag indicating whether all read requests are logged. + * + * @return + * A flag value of <code>true</code> if all read operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Read") public boolean isRead() { return read; } + /** + * Sets a flag indicating whether all read requests should be logged. + * + * @param read + * Set a flag value of <code>true</code> to log all read operations; otherwise, <code>false</code>. + */ public void setRead(boolean read) { this.read = read; } + /** + * Gets a flag indicating whether all delete requests are logged. + * + * @return + * A flag value of <code>true</code> if all delete operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Delete") public boolean isDelete() { return delete; } + /** + * Sets a flag indicating whether all delete requests should be logged. + * + * @param delete + * Set a flag value of <code>true</code> to log all delete operations; otherwise, <code>false</code>. + */ public void setDelete(boolean delete) { this.delete = delete; } + /** + * Gets the version of logging configured on the storage account. + * + * @return + * A {@link String} containing the version of logging configured on the storage account. + */ @XmlElement(name = "Version") public String getVersion() { return version; } + /** + * Sets the version of logging configured on the storage account. + * + * @param version + * A {@link String} containing the version of logging configured on the storage account. + */ public void setVersion(String version) { this.version = version; } } + /** + * Represents the metrics options that can be set on a storage account. + */ public static class Metrics { private String version; private boolean enabled; private Boolean includeAPIs; private RetentionPolicy retentionPolicy; + /** + * Gets the retention policy for metrics data set on the storage account. + * + * @return + * The {@link RetentionPolicy} set on the storage account. + */ @XmlElement(name = "RetentionPolicy") public RetentionPolicy getRetentionPolicy() { return retentionPolicy; } + /** + * Sets the retention policy to use for metrics data on the storage account. + * + * @param retentionPolicy + * The {@link RetentionPolicy} to set on the storage account. + */ public void setRetentionPolicy(RetentionPolicy retentionPolicy) { this.retentionPolicy = retentionPolicy; } + /** + * Gets a flag indicating whether metrics generates summary statistics for called API operations. + * + * @return + * A flag value of <code>true</code> if metrics generates summary statistics for called API operations; + * otherwise, <code>false</code>. + */ @XmlElement(name = "IncludeAPIs") public Boolean isIncludeAPIs() { return includeAPIs; } + /** + * Sets a flag indicating whether metrics should generate summary statistics for called API operations. This + * flag is optional if metrics is not enabled. + * + * @param includeAPIs + * Set a flag value of <code>true</code> to generate summary statistics for called API operations; + * otherwise, <code>false</code>. + */ public void setIncludeAPIs(Boolean includeAPIs) { this.includeAPIs = includeAPIs; } + /** + * Gets a flag indicating whether metrics is enabled for the storage account. + * + * @return + * A flag value of <code>true</code> if metrics is enabled for the storage account; otherwise, + * <code>false</code>. + */ @XmlElement(name = "Enabled") public boolean isEnabled() { return enabled; } + /** + * Sets a flag indicating whether to enable metrics for the storage account. + * + * @param enabled + * Set a flag value of <code>true</code> to enable metrics for the storage account; otherwise, + * <code>false</code>. + */ public void setEnabled(boolean enabled) { this.enabled = enabled; } + /** + * Gets the version of Storage Analytics configured on the storage account. + * + * @return + * A {@link String} containing the version of Storage Analytics configured on the storage account. + */ @XmlElement(name = "Version") public String getVersion() { return version; } + /** + * Sets the version of Storage Analytics configured on the storage account. + * + * @param version + * A {@link String} containing the version of Storage Analytics configured on the storage account. + */ public void setVersion(String version) { this.version = version; } } + /** + * Represents the optional retention policy that can be applied to logging or metrics on the storage account. + */ public static class RetentionPolicy { private boolean enabled; private Integer days; // nullable, because optional if "enabled" is false + /** + * Gets the number of days that metrics or logging data should be retained, if logging is enabled. + * + * @return + * The number of days to retain logging or metrics data if logging is enabled, or <code>null</code>. + */ @XmlElement(name = "Days") public Integer getDays() { return days; } + /** + * Sets the number of days that metrics or logging data should be retained. The minimum value you can specify is + * 1; the largest value is 365 (one year). This value must be specified even if the enabled flag is set + * to <code>false</code>. + * + * @param days + * The number of days to retain logging or metrics data. + */ public void setDays(Integer days) { this.days = days; } + /** + * Gets a flag indicating whether a retention policy is enabled. + * + * @return + * A flag value of <code>true</code> if a retention policy is enabled; otherwise, <code>false</code>. + */ @XmlElement(name = "Enabled") public boolean isEnabled() { return enabled; } + /** + * Sets a flag indicating whether to enable a retention policy. + * + * @param enabled + * Set a flag value of <code>true</code> to enable a retention policy; otherwise, <code>false</code>. + */ public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataResult.java index abda2abdfdb60..c00f3d67d981c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/SetBlobMetadataResult.java @@ -2,36 +2,85 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; import java.util.Date; +import com.microsoft.windowsazure.services.blob.BlobContract; + +/** + * A wrapper class for the response returned from a Blob Service REST API Set Blob Metadata operation. This is + * returned by calls to implementations of + * {@link BlobContract#setBlobMetadata(String, String, java.util.HashMap, SetBlobMetadataOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179414.aspx">Set Blob Metadata</a> + * documentation on MSDN for details of the underlying Blob Service REST API operation. + */ public class SetBlobMetadataResult { private String etag; private Date lastModified; + /** + * Gets the ETag of the blob. + * <p> + * This value can be used in an access condition when updating or deleting a blob to prevent the client from + * modifying data that has been changed by another client. + * + * @return + * A {@link String} containing the server-assigned ETag value for the blob. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> element returned in the + * response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the server-assigned ETag value for the blob. + */ public void setEtag(String etag) { this.etag = etag; } + /** + * Gets the last modified time of the blob. + * <p> + * Any operation that modifies the blob, including updates to the blob's metadata or properties, changes the last + * modified time of the blob. This value can be used in an access condition when updating or deleting a blob to + * prevent the client from modifying data that has been changed by another client. + * + * @return + * A {@link java.util.Date} containing the last modified time of the page blob. + */ public Date getLastModified() { return lastModified; } + /** + * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> + * element returned in the response. + * <p> + * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by + * the server. + * + * @param lastModified + * A {@link java.util.Date} containing the last modified time of the blob. + */ public void setLastModified(Date lastModified) { this.lastModified = lastModified; } From 887da24da88c7c8254ee152c6ee3d939daeef7c9 Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Mon, 30 Apr 2012 17:25:35 -0700 Subject: [PATCH 68/76] Add package file for Blob to Blob Javadocs build. --- .../com/microsoft/windowsazure/services/blob/package.html | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/package.html diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/package.html new file mode 100644 index 0000000000000..7795f32cd9e54 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the blob service class, interface, and associated configuration and utility classes. +</body> +</html> From 0f84dbb67ea104b19245fb535847f9e6fb4effff Mon Sep 17 00:00:00 2001 From: Joe Giardino <joegiard@microsoft.com> Date: Tue, 1 May 2012 10:54:18 -0700 Subject: [PATCH 69/76] Issue # 55 CloudTableClient should use Iterator instead of Iterable. Signed-off-by: Joe Giardino <joegiard@microsoft.com> --- .../services/blob/client/CloudBlobClient.java | 4 +- .../blob/client/CloudBlobContainer.java | 4 +- .../implementation/LazySegmentedIterable.java | 76 +++++++++++++++++++ .../implementation/LazySegmentedIterator.java | 11 +-- .../queue/client/CloudQueueClient.java | 4 +- .../table/client/CloudTableClient.java | 6 +- .../table/client/TableClientTests.java | 22 +++++- .../table/client/TableQueryTests.java | 49 ++++++++++++ 8 files changed, 155 insertions(+), 21 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterable.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobClient.java index 7b7ccd4e461f4..ed74ed27450c8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobClient.java @@ -36,7 +36,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; 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.LazySegmentedIterator; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ListingContext; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -534,7 +534,7 @@ public ResultSegment<CloudBlobContainer> execute(final CloudBlobClient client, f } }; - return new LazySegmentedIterator<CloudBlobClient, Void, CloudBlobContainer>(impl, this, null, + return new LazySegmentedIterable<CloudBlobClient, Void, CloudBlobContainer>(impl, this, null, options.getRetryPolicyFactory(), opContext); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java index 3892a425c1484..dffa565ca014b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java @@ -42,7 +42,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; 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.LazySegmentedIterator; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -1273,7 +1273,7 @@ public ResultSegment<ListBlobItem> execute(final CloudBlobClient client, } }; - return new LazySegmentedIterator<CloudBlobClient, CloudBlobContainer, ListBlobItem>(impl, + return new LazySegmentedIterable<CloudBlobClient, CloudBlobContainer, ListBlobItem>(impl, this.blobServiceClient, this, options.getRetryPolicyFactory(), opContext); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterable.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterable.java new file mode 100644 index 0000000000000..d57bb296fe284 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterable.java @@ -0,0 +1,76 @@ +/** + * Copyright 2011 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.core.storage.utils.implementation; + +import java.util.Iterator; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.RetryPolicyFactory; + +/** + * RESERVED FOR INTERNAL USE. Provides a lazy iterator which will retrieve the next segment of a result as the iterator + * is consumed + * + * @param <CLIENT_TYPE> + * The service client type + * @param <PARENT_TYPE> + * The type of the parent object, i.e. CloudBlobClient for ListContainers etc. + * @param <ENTITY_TYPE> + * The type of the objects the resulting iterable objects + */ +public final class LazySegmentedIterable<CLIENT_TYPE, PARENT_TYPE, ENTITY_TYPE> implements Iterable<ENTITY_TYPE> { + /** + * Holds the service client associated with the operations. + */ + private final CLIENT_TYPE client; + + /** + * Holds a reference to the parent object, i.e. CloudBlobContainer for list blobs. + */ + private final PARENT_TYPE parentObject; + + /** + * Holds the reference to the RetryPolicyFactory object. + */ + private final RetryPolicyFactory policyFactory; + + /** + * Holds the SegmentedStorageOperation which is used to retrieve the next segment of results. + */ + private final SegmentedStorageOperation<CLIENT_TYPE, PARENT_TYPE, ResultSegment<ENTITY_TYPE>> segmentGenerator; + + /** + * Holds an object used to track the execution of the operation + */ + private final OperationContext opContext; + + public LazySegmentedIterable( + final SegmentedStorageOperation<CLIENT_TYPE, PARENT_TYPE, ResultSegment<ENTITY_TYPE>> segmentGenerator, + final CLIENT_TYPE client, final PARENT_TYPE parent, final RetryPolicyFactory policyFactory, + final OperationContext opContext) { + this.segmentGenerator = segmentGenerator; + this.parentObject = parent; + this.opContext = opContext; + this.policyFactory = policyFactory; + this.client = client; + } + + @Override + public Iterator<ENTITY_TYPE> iterator() { + return new LazySegmentedIterator<CLIENT_TYPE, PARENT_TYPE, ENTITY_TYPE>(this.segmentGenerator, this.client, + this.parentObject, this.policyFactory, this.opContext); + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterator.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterator.java index b0525448bd381..e1f9f6ab116f4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterator.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LazySegmentedIterator.java @@ -34,8 +34,7 @@ * @param <ENTITY_TYPE> * The type of the objects the resulting iterable objects */ -public final class LazySegmentedIterator<CLIENT_TYPE, PARENT_TYPE, ENTITY_TYPE> implements Iterator<ENTITY_TYPE>, - Iterable<ENTITY_TYPE> { +public final class LazySegmentedIterator<CLIENT_TYPE, PARENT_TYPE, ENTITY_TYPE> implements Iterator<ENTITY_TYPE> { /** * Holds the current segment of results. @@ -126,14 +125,6 @@ public boolean hasNext() { return this.currentSegmentIterator.hasNext(); } - /** - * Gets a reference to the iterator. - */ - @Override - public Iterator<ENTITY_TYPE> iterator() { - return this; - } - /** * Returns the next element. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClient.java index 8b8fe0e571f20..8971ba9120323 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClient.java @@ -36,7 +36,7 @@ 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.LazySegmentedIterator; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ListingContext; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -164,7 +164,7 @@ public ResultSegment<CloudQueue> execute(final CloudQueueClient client, final Vo } }; - return new LazySegmentedIterator<CloudQueueClient, Void, CloudQueue>(impl, this, null, + return new LazySegmentedIterable<CloudQueueClient, Void, CloudQueue>(impl, this, null, options.getRetryPolicyFactory(), opContext); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java index b471594261c68..8932790d3c2a8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java @@ -39,7 +39,7 @@ 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.LazySegmentedIterator; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -1328,7 +1328,7 @@ public ResultSegment<T> execute(final CloudTableClient client, final TableQuery< } }; - return new LazySegmentedIterator<CloudTableClient, TableQuery<T>, T>(impl, this, queryRef, + return new LazySegmentedIterable<CloudTableClient, TableQuery<T>, T>(impl, this, queryRef, options.getRetryPolicyFactory(), opContext); } else { @@ -1352,7 +1352,7 @@ public ResultSegment<R> execute(final CloudTableClient client, final TableQuery< return result; } }; - return new LazySegmentedIterator<CloudTableClient, TableQuery<T>, R>(impl, this, queryRef, + return new LazySegmentedIterable<CloudTableClient, TableQuery<T>, R>(impl, this, queryRef, options.getRetryPolicyFactory(), opContext); } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java index 7dc7bd3c4dbd6..8060002ca8ae2 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java @@ -142,17 +142,35 @@ public void listTablesWithIterator() throws IOException, URISyntaxException, Sto try { // With prefix int currTable = 0; - for (String s : tClient.listTables(tableBaseName, null, null)) { + Iterable<String> listTables = tClient.listTables(tableBaseName, null, null); + for (String s : listTables) { Assert.assertEquals(s, String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); currTable++; } + Assert.assertEquals(20, currTable); + // Second Iteration + currTable = 0; + for (String s : listTables) { + Assert.assertEquals(s, + String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(currTable))); + currTable++; + } Assert.assertEquals(20, currTable); // Without prefix currTable = 0; - for (String s : tClient.listTables()) { + Iterable<String> listTablesNoPrefix = tClient.listTables(); + for (String s : listTablesNoPrefix) { + if (s.startsWith(tableBaseName)) { + currTable++; + } + } + + Assert.assertEquals(20, currTable); + currTable = 0; + for (String s : listTablesNoPrefix) { if (s.startsWith(tableBaseName)) { currTable++; } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java index dd46b99bae991..75740c9fff23f 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableQueryTests.java @@ -20,6 +20,7 @@ import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.security.InvalidKeyException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; @@ -59,6 +60,54 @@ public static void setup() throws URISyntaxException, StorageException, InvalidK } } + @Test + public void tableQueryIterateTwice() { + // Create entity to check against + class1 randEnt = TableTestBase.generateRandomEnitity(null); + + final Iterable<DynamicTableEntity> result = tClient.execute(TableQuery.from(testSuiteTableName, + DynamicTableEntity.class).take(50)); + + ArrayList<DynamicTableEntity> firstIteration = new ArrayList<DynamicTableEntity>(); + ArrayList<DynamicTableEntity> secondIteration = new ArrayList<DynamicTableEntity>(); + + // Validate results + for (DynamicTableEntity ent : result) { + Assert.assertEquals(ent.getProperties().size(), 4); + Assert.assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA()); + Assert.assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB()); + Assert.assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC()); + Assert.assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD())); + firstIteration.add(ent); + } + + // Validate results + for (DynamicTableEntity ent : result) { + Assert.assertEquals(ent.getProperties().size(), 4); + Assert.assertEquals(ent.getProperties().get("A").getValueAsString(), randEnt.getA()); + Assert.assertEquals(ent.getProperties().get("B").getValueAsString(), randEnt.getB()); + Assert.assertEquals(ent.getProperties().get("C").getValueAsString(), randEnt.getC()); + Assert.assertTrue(Arrays.equals(ent.getProperties().get("D").getValueAsByteArray(), randEnt.getD())); + secondIteration.add(ent); + } + + Assert.assertEquals(firstIteration.size(), secondIteration.size()); + for (int m = 0; m < firstIteration.size(); m++) { + Assert.assertEquals(firstIteration.get(m).getPartitionKey(), secondIteration.get(m).getPartitionKey()); + Assert.assertEquals(firstIteration.get(m).getRowKey(), secondIteration.get(m).getRowKey()); + Assert.assertEquals(firstIteration.get(m).getProperties().size(), secondIteration.get(m).getProperties() + .size()); + Assert.assertEquals(firstIteration.get(m).getProperties().get("A").getValueAsString(), + secondIteration.get(m).getProperties().get("A").getValueAsString()); + Assert.assertEquals(firstIteration.get(m).getProperties().get("B").getValueAsString(), + secondIteration.get(m).getProperties().get("B").getValueAsString()); + Assert.assertEquals(firstIteration.get(m).getProperties().get("C").getValueAsString(), + secondIteration.get(m).getProperties().get("C").getValueAsString()); + Assert.assertTrue(Arrays.equals(firstIteration.get(m).getProperties().get("D").getValueAsByteArray(), + secondIteration.get(m).getProperties().get("D").getValueAsByteArray())); + } + } + @Test public void tableQueryWithDynamicEntity() { // Create entity to check against From 51fd77191f52a64773465372a82720ffe91d59bd Mon Sep 17 00:00:00 2001 From: Joost de Nijs <joostden@microsoft.com> Date: Wed, 2 May 2012 10:59:26 -0700 Subject: [PATCH 70/76] updating changelog and pom.xml for 0.2.2 release --- ChangeLog.txt | 5 +++++ microsoft-azure-api/pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog.txt b/ChangeLog.txt index 3d99a067228f4..1b628284c7c66 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,8 @@ +2012.05.02 Version 0.2.2 + * Added Javadoc comments to Azure Blob Service Layer + * Fixed a URL encoding issue in Table Client Layer + * Made CloudTableClient use Iterator instead of Iterable + 2012.04.11 Version 0.2.1 * Added Service Layer support for Azure Table * Added Javadoc comments to Azure Queue Service Layer diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index 01567851ee74f..b9cb8f3ad694d 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -3,7 +3,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.microsoft.windowsazure</groupId> <artifactId>microsoft-windowsazure-api</artifactId> - <version>0.2.1</version> + <version>0.2.2</version> <packaging>jar</packaging> <name>Microsoft Windows Azure Client API</name> From 09f0ab227e948087bc68e30a7baf6cd44db9f64f Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Tue, 8 May 2012 17:36:41 -0700 Subject: [PATCH 71/76] Add Javadocs comments to Table service layer files, fix table entry in table client. --- .../services/table/TableContract.java | 433 ++++++++++++++++++ .../table/client/TableServiceEntity.java | 4 +- .../table/models/BatchOperations.java | 389 ++++++++++++++++ .../services/table/models/BatchResult.java | 89 ++++ .../services/table/models/BinaryFilter.java | 37 ++ .../services/table/models/ConstantFilter.java | 41 ++ .../table/models/DeleteEntityOptions.java | 23 + .../services/table/models/EdmType.java | 114 +++++ .../services/table/models/Entity.java | 238 ++++++++++ .../services/table/models/Filter.java | 177 +++++++ .../table/models/GetEntityResult.java | 27 ++ .../models/GetServicePropertiesResult.java | 32 ++ .../services/table/models/GetTableResult.java | 26 ++ .../table/models/InsertEntityResult.java | 27 ++ .../services/table/models/Property.java | 109 +++++ .../table/models/PropertyNameFilter.java | 31 ++ .../table/models/QueryEntitiesOptions.java | 170 +++++++ .../table/models/QueryEntitiesResult.java | 100 ++++ .../table/models/QueryStringFilter.java | 42 ++ .../table/models/QueryTablesOptions.java | 73 +++ .../table/models/QueryTablesResult.java | 63 +++ .../table/models/ServiceProperties.java | 181 ++++++++ .../services/table/models/TableEntry.java | 15 + .../table/models/TableServiceOptions.java | 3 + .../services/table/models/UnaryFilter.java | 28 ++ .../table/models/UpdateEntityResult.java | 33 ++ 26 files changed, 2503 insertions(+), 2 deletions(-) diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java index 2bb4e1a3ac6ae..e4a448f0f7ff6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -32,68 +32,501 @@ import com.microsoft.windowsazure.services.table.models.TableServiceOptions; import com.microsoft.windowsazure.services.table.models.UpdateEntityResult; +/** + * Defines the methods available on the Windows Azure Table storage service. Construct an object instance implementing + * <code>TableContract</code> with one of the static <em>create</em> methods on {@link TableService}. These methods + * associate a <code>Configuration</code> with the implementation, so the methods on the instance of + * <code>TableContract</code> all work with a particular storage account. + */ public interface TableContract extends FilterableService<TableContract> { + /** + * Gets the properties of a storage account’s Table service, including Windows Azure Storage Analytics. + * + * @return + * A {@link GetServicePropertiesResult} reference to the Table service properties. + * + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetServicePropertiesResult getServiceProperties() throws ServiceException; + /** + * Gets the properties of a storage account’s Table service, including Windows Azure Storage Analytics, using the + * specified options. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * A {@link GetServicePropertiesResult} reference to the Table service properties. + * + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException; + /** + * Sets the properties of a storage account’s Table service, including Windows Azure Storage Analytics. + * + * @param serviceProperties + * A {@link ServiceProperties} instance containing the Table service properties to set. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException; + /** + * Sets the properties of a storage account’s Table service, including Windows Azure Storage Analytics, using the + * specified options. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * + * @param serviceProperties + * A {@link ServiceProperties} instance containing the Table service properties to set. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException; + /** + * Creates a table with the specified name in the storage account. + * <p> + * Table names must be unique within a storage account, and must conform to these rules: + * <ul> + * <li>Table names may contain only alphanumeric characters.</li> + * <li>Table names cannot begin with a numeric character.</li> + * <li>Table names are case-insensitive.</li> + * <li>Table names must be from 3 to 63 characters long.</li> + * </ul> + * <p> + * These rules are also described by the regular expression "^[A-Za-z][A-Za-z0-9]{2,62}$". + * <p> + * Table names preserve the case with which they were created, but are case-insensitive when used. + * + * @param table + * A {@link String} containing the name of the table to create. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createTable(String table) throws ServiceException; + /** + * Creates a table with the specified name in the storage account, using the specified options. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * <p> + * Table names must be unique within a storage account, and must conform to these rules: + * <ul> + * <li>Table names may contain only alphanumeric characters.</li> + * <li>Table names cannot begin with a numeric character.</li> + * <li>Table names are case-insensitive.</li> + * <li>Table names must be from 3 to 63 characters long.</li> + * </ul> + * <p> + * These rules are also described by the regular expression "^[A-Za-z][A-Za-z0-9]{2,62}$". + * <p> + * Table names preserve the case with which they were created, but are case-insensitive when used. + * + * @param table + * A {@link String} containing the name of the table to create. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void createTable(String table, TableServiceOptions options) throws ServiceException; + /** + * Deletes the specified table and any data it contains from the storage account. + * <p> + * When a table is successfully deleted, it is immediately marked for deletion and is no longer accessible to + * clients. The table is later removed from the Table service during garbage collection. + * <p> + * Note that deleting a table is likely to take at least 40 seconds to complete. If an operation is attempted + * against the table while it is being deleted, the service returns status code 409 (Conflict), with additional + * error information indicating that the table is being deleted. This causes a {@link ServiceException} to be thrown + * in the context of the client request. + * + * @param table + * A {@link String} containing the name of the table to delete. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteTable(String table) throws ServiceException; + /** + * Deletes the specified table and any data it contains from the storage account, using the specified options. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * <p> + * When a table is successfully deleted, it is immediately marked for deletion and is no longer accessible to + * clients. The table is later removed from the Table service during garbage collection. + * <p> + * Note that deleting a table is likely to take at least 40 seconds to complete. If an operation is attempted + * against the table while it is being deleted, the service returns status code 409 (Conflict), with additional + * error information indicating that the table is being deleted. This causes a {@link ServiceException} to be thrown + * in the context of the client request. + * + * @param table + * A {@link String} containing the name of the table to delete. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteTable(String table, TableServiceOptions options) throws ServiceException; + /** + * Gets the specified table entry from the list of tables in the storage account. + * + * @param table + * A {@link String} containing the name of the table to retrieve. + * @return + * A {@link GetTableResult} instance containing the table entry returned. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetTableResult getTable(String table) throws ServiceException; + /** + * Gets the specified table entry from the list of tables in the storage account, using the specified options. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * + * @param table + * A {@link String} containing the name of the table to retrieve. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * A {@link GetTableResult} instance containing the table entry returned. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException; + /** + * Gets a list of tables in the storage account. + * + * @return + * A {@link QueryTablesResult} instance containing the list of table entries returned. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ QueryTablesResult queryTables() throws ServiceException; + /** + * Gets a list of tables in the storage account, using the specified options. + * <p> + * Use the {@link QueryTablesOptions options} parameter to specify options for the request, such as a filter to + * limit results to tables with certain properties, the next table name continuation token to use to resume the + * query tables request from, and a prefix string to match table names with. + * + * @param options + * A {@link QueryTablesOptions} instance containing options for the request. + * @return + * A {@link QueryTablesResult} instance containing the list of table entries returned. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException; + /** + * Inserts an entity into a table. + * + * @param table + * A {@link String} containing the name of the table to insert the entity into. + * @param entity + * An {@link Entity} instance containing the entity data to insert in the table. + * @return + * An {@link InsertEntityResult} containing the entity inserted in the table. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException; + /** + * Inserts an entity into a table, using the specified options. + * + * @param table + * A {@link String} containing the name of the table to insert the entity into. + * @param entity + * An {@link Entity} instance containing the entity data to insert in the table. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * An {@link InsertEntityResult} containing the entity inserted in the table. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + /** + * Updates an entity in a table. The entity data is completely replaced with the data in the <em>entity</em> + * parameter. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to update. + * @param entity + * An {@link Entity} instance containing the entity to update in the table. + * @return + * An {@link UpdateEntityResult} containing the ETag of the updated entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException; + /** + * Updates an entity in a table, using the specified options. The entity data is completely replaced with the data + * in the <em>entity</em> parameter. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to update. + * @param entity + * An {@link Entity} instance containing the entity to update in the table. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * An {@link UpdateEntityResult} containing the ETag of the updated entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + /** + * Merges entity data into an existing entity in a table. Property values in the existing entity are overwritten + * with matching properties in the <em>entity</em> parameter. Properties in the <em>entity</em> parameter that are + * not present in the existing entity are added to it. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to merge. + * @param entity + * An {@link Entity} instance containing the entity data to merge into the existing entity. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException; + /** + * Merges entity data into an existing entity in a table, using the specified options. Property values in the + * existing entity are overwritten with matching properties in the <em>entity</em> parameter. Properties in the + * <em>entity</em> parameter that are not present in the existing entity are added to it. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to merge. + * @param entity + * An {@link Entity} instance containing the entity data to merge into the existing entity. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + /** + * Inserts or replaces an entity in a table. If the table does not contain an entity with a matching primary key, it + * is inserted. Otherwise, the entity data is completely replaced with the data in the <em>entity</em> parameter. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or replace. + * @param entity + * An {@link Entity} instance containing the entity data to insert or replace in the table. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException; + /** + * Inserts or replaces an entity in a table, using the specified options. If the table does not contain an entity + * with a matching primary key, it is inserted. Otherwise, the entity data is completely replaced with the data in + * the <em>entity</em> parameter. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or replace. + * @param entity + * An {@link Entity} instance containing the entity data to insert or replace in the table. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + /** + * Inserts or merges an entity in a table. If the table does not contain an entity with a matching primary key, it + * is inserted. Otherwise, property values in the existing entity are overwritten with matching properties in the + * <em>entity</em> parameter. Properties in the <em>entity</em> parameter that are not present in the existing + * entity are added to it. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or merge. + * @param entity + * An {@link Entity} instance containing the entity data to insert or merge in the table. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException; + /** + * Inserts or merges an entity in a table, using the specified options. If the table does not contain an entity with + * a matching primary key, it is inserted. Otherwise, property values in the existing entity are overwritten with + * matching properties in the <em>entity</em> parameter. Properties in the <em>entity</em> parameter that are not + * present in the existing entity are added to it. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or merge. + * @param entity + * An {@link Entity} instance containing the entity data to insert or merge in the table. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * An {@link UpdateEntityResult} containing the ETag of the modified entity. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException; + /** + * Deletes an entity from a table. + * + * @param table + * A {@link String} containing the name of the table to delete the entity from. + * @param partitionKey + * A {@link String} containing the partition key of the entity to delete. + * @param rowKey + * A {@link String} containing the row key of the entity to delete. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException; + /** + * Deletes an entity from a table, using the specified options. + * <p> + * Use the {@link DeleteEntityOptions options} parameter to specify an ETag value that must match to delete the + * entity. + * + * @param table + * A {@link String} containing the name of the table to delete the entity from. + * @param partitionKey + * A {@link String} containing the partition key of the entity to delete. + * @param rowKey + * A {@link String} containing the row key of the entity to delete. + * @param options + * A {@link DeleteEntityOptions} instance containing the ETag to match with the entity to delete. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options) throws ServiceException; + /** + * Gets the specified entity. + * + * @param table + * A {@link String} containing the name of the table to get the entity from. + * @param partitionKey + * A {@link String} containing the partition key of the entity to get. + * @param rowKey + * A {@link String} containing the row key of the entity to get. + * @return + * A {@link GetEntityResult} instance containing the entity data returned in the server response. + * @throws ServiceException + */ GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException; + /** + * Gets the specified entity, using the specified options. + * + * @param table + * A {@link String} containing the name of the table to get the entity from. + * @param partitionKey + * A {@link String} containing the partition key of the entity to get. + * @param rowKey + * A {@link String} containing the row key of the entity to get. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * A {@link GetEntityResult} instance containing the entity data returned in the server response. + * @throws ServiceException + */ GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options) throws ServiceException; + /** + * Lists the entities in a table. + * + * @param table + * A {@link String} containing the name of the table to retrieve the list of entities from. + * @return + * A {@link QueryEntitiesResult} instance containing the server response to the batch request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ QueryEntitiesResult queryEntities(String table) throws ServiceException; + /** + * Lists the entities in a table that match the specified options. + * <p> + * Use the {@link QueryEntitiesOptions options} parameter to specify the next partition key and next row key + * continuation tokens to use to resume the query entities request from, a collection of the property names to + * include in the entities returned in the server response, a filter to limit results to entities with certain + * property values, and a top count to limit the response to that number of the first matching results. + * + * @param table + * A {@link String} containing the name of the table to retrieve the list of entities from. + * @param options + * A {@link QueryEntitiesOptions} instance containing options for the request. + * @return + * A {@link QueryEntitiesResult} instance containing the server response to the batch request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException; + /** + * Submits multiple entity operations in the same table and partition group as a single transaction. Multiple insert + * entity, update entity, merge entity, delete entity, insert or replace entity, and insert or merge entity + * operations are supported within a single transaction. + * + * @param operations + * A {@link BatchOperations} instance containing the list of operations to send as a single transaction. + * @return + * A {@link BatchResult} instance containing the server response to the batch request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ BatchResult batch(BatchOperations operations) throws ServiceException; + /** + * Submits multiple entity operations in the same table and partition group as a single transaction, using the + * specified options. Multiple insert entity, update entity, merge entity, delete entity, insert or replace entity, + * and insert or merge entity operations are supported within a single transaction. + * <p> + * Use the {@link TableServiceOptions options} parameter to specify options for the request. + * + * @param operations + * A {@link BatchOperations} instance containing the list of operations to send as a single transaction. + * @param options + * A {@link TableServiceOptions} instance containing options for the request. + * @return + * A {@link BatchResult} instance containing the server response to the batch request. + * @throws ServiceException + * if an error occurs accessing the storage service. + */ BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java index d958bb328444b..04f26b84a3002 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableServiceEntity.java @@ -78,8 +78,8 @@ * <tr> * <td><strong>Edm.Byte</strong></td> * <td>{@link EdmType#BYTE}</td> - * <td><code>boolean, Boolean</code></td> - * <td>A Boolean value.</td> + * <td><code>byte, Byte</code></td> + * <td>An 8-bit integer value.</td> * </tr> * <tr> * <td><strong>Edm.DateTime</strong></td> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index 5de01c01542a8..f5e8b072f4c9f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -17,203 +17,592 @@ import java.util.ArrayList; import java.util.List; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the collection of table operations that may be sent as a single batch transaction with a + * {@link TableContract#batch(BatchOperations)} or {@link TableContract#batch(BatchOperations, TableServiceOptions)} + * request. A batch transaction is executed by the Storage Service REST API as a single atomic operation, by invoking an + * <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Entity Group Transaction</a>. + * <p> + * A batch operation may contain up to 100 individual table operations, with the requirement that the target entity of + * each operation must have same partition key. A batch with a query operation cannot contain any other operations. Note + * that the total payload of a batch operation is limited to 4MB. + * <p> + * The semantics for entity group transactions are defined by the <a + * href="http://msdn.microsoft.com/en-us/library/dd744839.aspx">WCF Data Services Batching Operations</a>. The WCF Data + * Services specification defines the following concepts for batch requests: + * <ul> + * <li>A <em>change set</em> is a group of one or more insert, update, or delete operations.</li> + * <li>A <em>batch</em> is a container of operations, including one or more change sets and query operations.</li> + * </ul> + * <p> + * The Table service supports a subset of the functionality defined by WCF Data Services: + * <ul> + * <li>The Table service supports only a single change set within a batch. The change set can include multiple insert, + * update, and delete operations. If a batch includes more than one change set, the first change set will be processed + * by the service, and additional change sets will be rejected with status code 400 (Bad Request). + * <p> + * <strong>Important</strong> Multiple operations against a single entity are not permitted within a change set.</li> + * <li>Note that a query operation is not permitted within a batch that contains insert, update, or delete operations; + * it must be submitted singly in the batch.</li> + * <li>Operations within a change set are processed atomically; that is, all operations in the change set either succeed + * or fail. Operations are processed in the order they are specified in the change set.</li> + * <li>The Table service does not support linking operations in a change set.</li> + * <li>The Table service supports a maximum of 100 operations in a change set.</li> + * <ul> + * <p> + * An individual request within the change set is identical to a request made when that operation is being called by + * itself. + * <p> + * To specify an update, merge, or delete operation only succeeds when the entity has not changed since it was last seen + * by the client, include the entities' ETag value in the {@link Entity} instance passed to the operation in the change + * set. + */ public class BatchOperations { private List<Operation> operations = new ArrayList<Operation>(); + /** + * Gets the collection of table operations in the batch. + * + * @return + * A {@link java.util.List} of {@link Operation} instances representing the table operations in the batch. + */ public List<Operation> getOperations() { return operations; } + /** + * Sets the collection of table operations in the batch. + * + * @param operations + * A {@link java.util.List} of {@link Operation} instances representing the table operations in the + * batch. + */ public void setOperations(List<Operation> operations) { this.operations = operations; } + /** + * Adds an insert entity operation to the collection of table operations in the batch. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>entity</em> parameter must have the same partition key as all other entities in the batch. + * + * @param table + * A {@link String} containing the name of the table to insert the entity into. + * @param entity + * The {@link Entity} instance to insert into the table. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addInsertEntity(String table, Entity entity) { this.operations.add(new InsertEntityOperation().setTable(table).setEntity(entity)); return this; } + /** + * Adds an update entity operation to the collection of table operations in the batch. An update operation replaces + * an existing entity with with the same primary key as the <em>entity</em> parameter. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>entity</em> parameter must have the same partition key as all other entities in the batch. + * + * @param table + * A {@link String} containing the name of the table to update the entity in. + * @param entity + * The {@link Entity} instance to update in the table. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addUpdateEntity(String table, Entity entity) { this.operations.add(new UpdateEntityOperation().setTable(table).setEntity(entity)); return this; } + /** + * Adds a merge entity operation to the collection of table operations in the batch. A merge operation replaces + * and inserts properties in an existing entity with with the same primary key as the <em>entity</em> parameter. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>entity</em> parameter must have the same partition key as all other entities in the batch. + * + * @param table + * A {@link String} containing the name of the table to merge the entity in. + * @param entity + * The {@link Entity} instance to merge in the table. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addMergeEntity(String table, Entity entity) { this.operations.add(new MergeEntityOperation().setTable(table).setEntity(entity)); return this; } + /** + * Adds an insert or replace entity operation to the collection of table operations in the batch. An insert or + * replace operation replaces an existing entity with with the same primary key as the <em>entity</em> parameter, or + * inserts the entity if no matching entity exists in the table. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>entity</em> parameter must have the same partition key as all other entities in the batch. + * + * @param table + * A {@link String} containing the name of the table to insert or replace the entity in. + * @param entity + * The {@link Entity} instance to insert or replace in the table. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addInsertOrReplaceEntity(String table, Entity entity) { this.operations.add(new InsertOrReplaceEntityOperation().setTable(table).setEntity(entity)); return this; } + /** + * Adds an insert or merge entity operation to the collection of table operations in the batch. An insert or + * merge operation replaces and inserts properties in an existing entity with with the same primary key as the + * <em>entity</em> parameter, or inserts the entity if no matching entity exists in the table. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>entity</em> parameter must have the same partition key as all other entities in the batch. + * + * @param table + * A {@link String} containing the name of the table to insert or replace the entity in. + * @param entity + * The {@link Entity} instance to insert or replace in the table. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { this.operations.add(new InsertOrMergeEntityOperation().setTable(table).setEntity(entity)); return this; } + /** + * Adds a delete entity operation to the collection of table operations in the batch. The delete operation removes + * the entity with with the specified partition key, row key, and ETag from the table. + * <p> + * The <em>table</em> parameter must refer to the same table as all other operations in the batch, and the + * <em>partitionKey</em> parameter must be the same partition key in all other operations in the batch. + * + * @param table + * A {@link String} containing the name of the table to delete the entity in. + * @param partitionKey + * A {@link String} containing the partition key of the entity to delete. + * @param rowKey + * A {@link String} containing the row key of the entity to delete. + * @param etag + * A {@link String} containing the ETag value of the entity to delete. + * @return + * A reference to this {@link BatchOperations} instance. + */ public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey, String etag) { this.operations.add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey) .setEtag(etag)); return this; } + /** + * The abstract base class for all batch operations. + */ public static abstract class Operation { } + /** + * Represents the parameters needed for an insert entity batch operation. + */ public static class InsertEntityOperation extends Operation { private String table; private Entity entity; + /** + * Gets the table name parameter for the insert entity batch operation set in this {@link InsertEntityOperation} + * instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to insert. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the insert entity batch operation. Note that this value must be the same + * for + * all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert. + * @return + * A reference to this {@link InsertEntityOperation} instance. + */ public InsertEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the entity parameter for the insert entity batch operation set in this {@link InsertEntityOperation} + * instance. + * + * @return + * The {@link Entity} instance containing the entity data to be inserted. + */ public Entity getEntity() { return entity; } + /** + * Sets the entity parameter for the insert entity batch operation. Note that the partition key value + * in the entity must be the same for all operations in the batch. + * + * @param entity + * The {@link Entity} instance containing the entity data to be inserted. + * @return + * A reference to this {@link InsertEntityOperation} instance. + */ public InsertEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the parameters needed for an update entity batch operation. + */ public static class UpdateEntityOperation extends Operation { private String table; private Entity entity; + /** + * Gets the table name parameter for the update entity batch operation set in this {@link UpdateEntityOperation} + * instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to update. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the update entity batch operation. Note that this value must be the same + * for + * all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to update. + * @return + * A reference to this {@link UpdateEntityOperation} instance. + */ public UpdateEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the entity parameter for the update entity batch operation set in this {@link UpdateEntityOperation} + * instance. + * + * @return + * The {@link Entity} instance containing the entity data to be updated. + */ public Entity getEntity() { return entity; } + /** + * Sets the entity parameter for the update entity batch operation. Note that the partition key value + * in the entity must be the same for all operations in the batch. + * + * @param entity + * The {@link Entity} instance containing the entity data to be updated. + * @return + * A reference to this {@link UpdateEntityOperation} instance. + */ public UpdateEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the parameters needed for a merge entity batch operation. + */ public static class MergeEntityOperation extends Operation { private String table; private Entity entity; + /** + * Gets the table name parameter for the merge entity batch operation set in this {@link MergeEntityOperation} + * instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to merge. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the merge entity batch operation. Note that this value must be the same for + * all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to merge. + * @return + * A reference to this {@link MergeEntityOperation} instance. + */ public MergeEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the entity parameter for the merge entity batch operation set in this {@link MergeEntityOperation} + * instance. + * + * @return + * The {@link Entity} instance containing the entity data to be merged. + */ public Entity getEntity() { return entity; } + /** + * Sets the entity parameter for the merge entity batch operation. Note that the partition key value + * in the entity must be the same for all operations in the batch. + * + * @param entity + * The {@link Entity} instance containing the entity data to be merged. + * @return + * A reference to this {@link MergeEntityOperation} instance. + */ public MergeEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the parameters needed for an insert or replace entity batch operation. + */ public static class InsertOrReplaceEntityOperation extends Operation { private String table; private Entity entity; + /** + * Gets the table name parameter for the insert or replace entity batch operation set in this + * {@link InsertOrReplaceEntityOperation} instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to insert or replace. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the insert or replace entity batch operation. Note that this value must be + * the same for all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or replace. + * @return + * A reference to this {@link InsertOrReplaceEntityOperation} instance. + */ public InsertOrReplaceEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the entity parameter for the insert or replace entity batch operation set in this + * {@link InsertOrReplaceEntityOperation} instance. + * + * @return + * The {@link Entity} instance containing the entity data to be inserted or replaced. + */ public Entity getEntity() { return entity; } + /** + * Sets the entity parameter for the insert or replace entity batch operation. Note that the partition key value + * in the entity must be the same for all operations in the batch. + * + * @param entity + * The {@link Entity} instance containing the entity data to be inserted or replaced. + * @return + * A reference to this {@link InsertOrReplaceEntityOperation} instance. + */ public InsertOrReplaceEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the parameters needed for an insert or merge entity batch operation. + */ public static class InsertOrMergeEntityOperation extends Operation { private String table; private Entity entity; + /** + * Gets the table name parameter for the insert or merge entity batch operation set in this + * {@link InsertOrMergeEntityOperation} instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to insert or merge. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the insert or merge entity batch operation. Note that this value must be + * the same for all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to insert or merge. + * @return + * A reference to this {@link InsertOrMergeEntityOperation} instance. + */ public InsertOrMergeEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the entity parameter for the insert or merge entity batch operation set in this + * {@link InsertOrMergeEntityOperation} instance. + * + * @return + * The {@link Entity} instance containing the entity data to be inserted or merged. + */ public Entity getEntity() { return entity; } + /** + * Sets the entity parameter for the insert or merge entity batch operation. Note that the partition key value + * in the entity must be the same for all operations in the batch. + * + * @param entity + * The {@link Entity} instance containing the entity data to be inserted or merged. + * @return + * A reference to this {@link InsertOrMergeEntityOperation} instance. + */ public InsertOrMergeEntityOperation setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the parameters needed for a delete entity operation. + */ public static class DeleteEntityOperation extends Operation { private String table; private String partitionKey; private String rowKey; private String etag; + /** + * Gets the table name parameter for the delete entity batch operation set in this {@link DeleteEntityOperation} + * instance. + * + * @return + * A {@link String} containing the name of the table that contains the entity to delete. + */ public String getTable() { return table; } + /** + * Sets the table name parameter for the delete entity batch operation. Note that this value must be the same + * for all operations in the batch. + * + * @param table + * A {@link String} containing the name of the table that contains the entity to delete. + * @return + * A reference to this {@link DeleteEntityOperation} instance. + */ public DeleteEntityOperation setTable(String table) { this.table = table; return this; } + /** + * Gets the partition key parameter for the delete entity batch operation set in this + * {@link DeleteEntityOperation} instance. + * + * @return + * A {@link String} containing the partition key value of the entity to delete. + */ public String getPartitionKey() { return partitionKey; } + /** + * Sets the partition key parameter for the delete entity batch operation. Note that this value must be the same + * for all operations in the batch. + * + * @param partitionKey + * A {@link String} containing the partition key value of the entity to delete. + * @return + * A reference to this {@link DeleteEntityOperation} instance. + */ public DeleteEntityOperation setPartitionKey(String partitionKey) { this.partitionKey = partitionKey; return this; } + /** + * Gets the row key parameter for the delete entity batch operation set in this {@link DeleteEntityOperation} + * instance. + * + * @return + * A {@link String} containing the row key value of the entity to delete. + */ public String getRowKey() { return rowKey; } + /** + * Sets the row key parameter for the delete entity batch operation. + * + * @param rowKey + * A {@link String} containing the row key value of the entity to delete. + * @return + * A reference to this {@link DeleteEntityOperation} instance. + */ public DeleteEntityOperation setRowKey(String rowKey) { this.rowKey = rowKey; return this; } + /** + * Gets the ETag parameter for the delete entity batch operation set in this {@link DeleteEntityOperation} + * instance. + * + * @return + * A {@link String} containing the ETag value of the entity to delete. + */ public String getEtag() { return etag; } + /** + * Sets the ETag parameter for the delete entity batch operation. + * + * @param etag + * A {@link String} containing the ETag value of the entity to delete. + * @return + * A reference to this {@link DeleteEntityOperation} instance. + */ public DeleteEntityOperation setEtag(String etag) { this.etag = etag; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java index 6ca4400068bb0..1d0a0b006d03b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -18,59 +18,148 @@ import java.util.List; import com.microsoft.windowsazure.services.core.ServiceException; +import com.microsoft.windowsazure.services.table.TableContract; +/** + * Represents the response to a request for a batch transaction returned from a Table Service REST API Entity Group + * Transaction operation. This is returned by calls to implementations of {@link TableContract#batch(BatchOperations)} + * and {@link TableContract#batch(BatchOperations, TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894038.aspx">Performing Entity Group + * Transactions</a> documentation on MSDN for details of the underlying Table Service REST API operation. + */ public class BatchResult { private List<Entry> entries = new ArrayList<Entry>(); + /** + * Gets the collection of {@link Entry} results from each MIME change set response corresponding to each request in + * the batch transaction. + * + * @return + * A {@link java.util.List} of {@link Entry} instances corresponding to the responses to each request in + * the batch transaction. + */ public List<Entry> getEntries() { return entries; } + /** + * Reserved for internal use. Sets the collection of {@link Entry} results from each MIME change set response + * corresponding to each request in the batch transaction. + * + * @param entries + * The {@link java.util.List} of {@link Entry} instances corresponding to the responses to each request + * in the batch transaction. + * @return + * A reference to this {@link BatchResult} instance. + */ public BatchResult setEntries(List<Entry> entries) { this.entries = entries; return this; } + /** + * The abstract base class for entries in the batch transaction response. + */ public static abstract class Entry { } + /** + * Represents the result of an insert entity operation within a batch transaction. + */ public static class InsertEntity extends Entry { private Entity entity; + /** + * Gets the table entity inserted by the operation as returned in the server response. + * + * @return + * The {@link Entity} returned in the server response. + */ public Entity getEntity() { return entity; } + /** + * Reserved for internal use. Sets the table entity inserted by the operation from the matching + * <strong>entry</strong> element in the MIME change set response corresponding to the insert request in the + * batch transaction. + * + * @param entity + * The {@link Entity} returned in the server response. + * @return + * A reference to this {@link InsertEntity} instance. + */ public InsertEntity setEntity(Entity entity) { this.entity = entity; return this; } } + /** + * Represents the result of an update entity operation within a batch transaction. + */ public static class UpdateEntity extends Entry { private String etag; + /** + * Gets the updated ETag value for the entity updated by the operation as returned in the server response. + * + * @return + * A {@link String} containing the updated ETag value for the entity. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag for the entity updated by the operation from the matching + * <strong>ETag</strong> header within the MIME change set response corresponding to the update request in the + * batch transaction. + * + * @param etag + * A {@link String} containing the updated ETag value for the entity. + * @return + * A reference to this {@link UpdateEntity} instance. + */ public UpdateEntity setEtag(String etag) { this.etag = etag; return this; } } + /** + * Represents the result of a delete entity operation within a batch transaction. + */ public static class DeleteEntity extends Entry { } + /** + * Represents an error result for an insert, update, or delete entity operation within a batch transaction. + */ public static class Error extends Entry { private ServiceException error; + /** + * Gets the {@link ServiceException} instance corresponding to the error returned in the server response. + * + * @return + * A @link ServiceException} instance corresponding to the error returned in the server response. + */ public ServiceException getError() { return error; } + /** + * Reserved for internal use. Sets the {@link ServiceException} instance created in response to an error result + * within the MIME change set response corresponding to the request in the batch transaction. + * + * @param error + * A @link ServiceException} instance corresponding to the error returned in the server response. + * @return + * A reference to this {@link Error} instance. + */ public Error setError(ServiceException error) { this.error = error; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java index 7666da869e056..96a1a6a71b570 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java @@ -14,25 +14,62 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a table query filter expression consisting of a filter, a binary comparison operator, and a filter. Use + * the static factory methods in the {@link Filter} class to create <code>BinaryFilter</code> instances, rather + * than constructing them directly. + */ public class BinaryFilter extends Filter { private final String operator; private final Filter left; private final Filter right; + /** + * Creates a <code>BinaryFilter</code> expression from a {@link Filter}, a binary comparison operator, and a + * {@link Filter}. + * <p> + * Use the static factory methods in the {@link Filter} class to create <code>BinaryFilter</code> instances, rather + * than constructing them directly. + * + * @param left + * The {@link Filter} to use on the left hand side of the expression. + * @param operator + * A {@link String} containing the comparison operator to use in the expression. + * @param right + * The {@link Filter} to use on the right hand side of the expression. + */ public BinaryFilter(Filter left, String operator, Filter right) { this.left = left; this.operator = operator; this.right = right; } + /** + * Gets the comparison operator to use in the <code>BinaryFilter</code> expression. + * + * @return + * A {@link String} containing the comparison operator to use in the expression. + */ public String getOperator() { return operator; } + /** + * Gets the {@link Filter} to use on the left hand side of the <code>BinaryFilter</code> expression. + * + * @return + * A {@link Filter} to use on the left hand side of the expression. + */ public Filter getLeft() { return left; } + /** + * Gets the {@link Filter} to use on the right hand side of the <code>BinaryFilter</code> expression. + * + * @return + * A {@link Filter} to use on the right hand side of the expression. + */ public Filter getRight() { return right; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java index aa3dd041be7cc..55b80c75531ed 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -14,13 +14,54 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a constant value used as a filter parameter in a table query request. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link ConstantFilter}, rather than + * constructing one directly. + * <p> + * Use this class to pass a constant value as a filter parameter. The value is passed as an <code>Object</code> but must + * be (or have an implicit conversion to) one of the following for successful serialization: + * <ul> + * <li><code>null</code></li> + * <li><code>byte[]</code></li> + * <li><code>Byte[]</code></li> + * <li><code>Date</code></li> + * <li><code>Long</code></li> + * <li><code>String</code></li> + * <li><code>UUID</code></li> + * </ul> + * <p> + * A {@link PropertyNameFilter} may be combined in a {@link BinaryFilter} with a comparison operator and a + * {@link ConstantFilter} to limit query results to properties with values that match the {@link ConstantFilter} value. + * The table service does not support wildcard queries, but you can perform prefix matching by using comparison + * operators on the desired prefix created as a {@link String} in a {@link ConstantFilter} instance. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a> + * topic in MSDN for more information on creating table query filter strings. + */ public class ConstantFilter extends Filter { private final Object value; + /** + * Creates a table query filter constant from the <em>value</em> parameter. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link ConstantFilter}, rather than + * constructing one directly. + * + * @param propertyName + * An {@link Object} containing the constant value to use as a filter parameter in a table query request. + */ public ConstantFilter(Object value) { this.value = value; } + /** + * Gets the constant filter value set in this {@link ConstantFilter} instance. + * + * @return + * The {@link Object} containing the constant value to use as a filter parameter in a table query request. + */ public Object getValue() { return value; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java index 4cce1e14a24a9..2be63a3d9e6bb 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java @@ -14,13 +14,36 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the options that may be set on a + * {@link TableContract#deleteEntity(String, String, String, DeleteEntityOptions)} request. An optional ETag value may + * be set to require that the deleted entity have the same ETag value to be deleted. Set a <code>null</code> ETag value + * to delete the entity unconditionally. + */ public class DeleteEntityOptions extends TableServiceOptions { private String etag; + /** + * Gets the ETag value to match in order to delete the entity set in this {@link DeleteEntityOptions} instance. + * + * @return + * A {@link String} containing the ETag value the entity must match to be deleted. + */ public String getEtag() { return etag; } + /** + * Sets the ETag value to match in order to delete the entity. Set the <em>etag</em> parameter to <code>null</code> + * to delete the entity unconditionally. + * + * @param etag + * A {@link String} containing the ETag value the entity must match to be deleted, or <code>null</code>. + * @return + * A reference to this {@link DeleteEntityOptions} instance. + */ public DeleteEntityOptions setEtag(String etag) { this.etag = etag; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java index 0f0585f2e22a9..22e3dda149c82 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java @@ -14,13 +14,127 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * A class containing string constants used to represent the primitive types of the Entity Data Model (EDM) in the Open + * Data Protocol (OData). The EDM is the underlying abstract data model used by OData services. The subset defined in + * this class is used for data type definitions of the properties of a table entity in the Windows Azure Storage + * service. + * <p> + * The following table shows the supported property data types in Windows Azure storage and the corresponding Java types + * when deserialized. + * <TABLE BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0"> + * <TR BGCOLOR="#EEEEFF" CLASS="TableSubHeadingColor"> + * <th>Storage Type</th> + * <th>EdmType Value</th> + * <th>Java Type</th> + * <th>Description</th> + * </tr> + * <tr> + * <td><strong>Edm.Binary</strong></td> + * <td>{@link EdmType#BINARY}</td> + * <td><code>byte[], Byte[]</code></td> + * <td>An array of bytes up to 64 KB in size.</td> + * </tr> + * <tr> + * <td><strong>Edm.Boolean</strong></td> + * <td>{@link EdmType#BOOLEAN}</td> + * <td><code>boolean, Boolean</code></td> + * <td>A Boolean value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Byte</strong></td> + * <td>{@link EdmType#BYTE}</td> + * <td><code>byte, Byte</code></td> + * <td>An 8-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.DateTime</strong></td> + * <td>{@link EdmType#DATE_TIME}</td> + * <td><code>Date</code></td> + * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, + * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> + * </tr> + * <tr> + * <td><strong>Edm.Double</strong></td> + * <td>{@link EdmType#DOUBLE}</td> + * <td><code>double, Double</code></td> + * <td>A 64-bit double-precision floating point value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Guid</strong></td> + * <td>{@link EdmType#GUID}</td> + * <td><code>UUID</code></td> + * <td>A 128-bit globally unique identifier.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int32</strong></td> + * <td>{@link EdmType#INT32}</td> + * <td><code>int, Integer</code></td> + * <td>A 32-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int64</strong></td> + * <td>{@link EdmType#INT64}</td> + * <td><code>long, Long</code></td> + * <td>A 64-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.String</strong></td> + * <td>{@link EdmType#STRING}</td> + * <td><code>String</code></td> + * <td>A UTF-16-encoded value. String values may be up to 64 KB in size.</td> + * </tr> + * </table> + * <p> + * For more information about OData, see the <a href="http://www.odata.org/">Open Data Protocol</a> website. + * <p> + * For an overview of the available EDM primitive data types and names, see the <a + * href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of the + * <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>. + * <p> + * The Abstract Type System used to define the primitive types supported by OData is defined in detail in <a + * href="http://msdn.microsoft.com/en-us/library/dd541474.aspx">[MC-CSDL] (section 2.2.1). + */ public class EdmType { + /** + * <strong>Edm.DateTime</strong> Represents date and time with values ranging from 12:00:00 midnight, January 1, + * 1753 A.D. through 11:59:59 P.M, December 9999 A.D. + */ public static final String DATETIME = "Edm.DateTime"; + + /** + * <strong>Edm.Binary</strong> Represents fixed- or variable-length binary data. + */ public static final String BINARY = "Edm.Binary"; + + /** + * <strong>Edm.Boolean</strong> Represents the mathematical concept of binary-valued logic. + */ public static final String BOOLEAN = "Edm.Boolean"; + + /** + * <strong>Edm.Double</strong> Represents a floating point number with 15 digits precision that can represent values + * with approximate range of +/- 2.23e -308 through +/- 1.79e +308. + */ public static final String DOUBLE = "Edm.Double"; + + /** + * <strong>Edm.Guid</strong> Represents a 16-byte (128-bit) unique identifier value. + */ public static final String GUID = "Edm.Guid"; + + /** + * <strong>Edm.Int32</strong> Represents a signed 32-bit integer value. + */ public static final String INT32 = "Edm.Int32"; + + /** + * <strong>Edm.Int64</strong> Represents a signed 64-bit integer value. + */ public static final String INT64 = "Edm.Int64"; + + /** + * <strong>Edm.String</strong> Represents fixed- or variable-length character data. + */ public static final String STRING = "Edm.String"; } \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java index f74bda6d7003f..cd8d483f99422 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -18,72 +18,310 @@ import java.util.HashMap; import java.util.Map; +/** + * Represents an element of structured storage in a table. Tables store data as a collection of uniquely identifiable + * entities. Entities are similar to rows in a database. An entity has a primary key and a set of properties. A property + * is a name, typed-value pair, similar to a database column. + * <p> + * The Table service does not enforce any schema for tables, so two entities in the same table may have different sets + * of properties. Developers may choose to enforce a schema on the client side. A table may contain any number of + * entities. + * <p> + * An entity always has the following system properties: + * <ul> + * <li><strong>PartitionKey</strong> property</li> + * <li><strong>RowKey</strong> property</li> + * <li><strong>Timestamp</strong> property</li> + * </ul> + * These system properties are automatically included for every entity in a table. The names of these properties are + * reserved and cannot be changed. The developer is responsible for inserting and updating the values of + * <strong>PartitionKey</strong> and <strong>RowKey</strong>. The server manages the value of + * <strong>Timestamp</strong>, which cannot be modified. + * <p> + * An entity can have up to 255 properties, including the three system properties. Therefore, the user may include up to + * 252 custom properties, in addition to the three system properties. The combined size of all data in an entity's + * properties cannot exceed 1 MB. + * <p> + * The following table shows the supported property data types in Windows Azure storage and the corresponding Java types + * when deserialized. + * <TABLE BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0"> + * <TR BGCOLOR="#EEEEFF" CLASS="TableSubHeadingColor"> + * <th>Storage Type</th> + * <th>EdmType Value</th> + * <th>Java Type</th> + * <th>Description</th> + * </tr> + * <tr> + * <td><strong>Edm.Binary</strong></td> + * <td>{@link EdmType#BINARY}</td> + * <td><code>byte[], Byte[]</code></td> + * <td>An array of bytes up to 64 KB in size.</td> + * </tr> + * <tr> + * <td><strong>Edm.Boolean</strong></td> + * <td>{@link EdmType#BOOLEAN}</td> + * <td><code>boolean, Boolean</code></td> + * <td>A Boolean value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Byte</strong></td> + * <td>{@link EdmType#BYTE}</td> + * <td><code>byte, Byte</code></td> + * <td>An 8-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.DateTime</strong></td> + * <td>{@link EdmType#DATE_TIME}</td> + * <td><code>Date</code></td> + * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, + * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> + * </tr> + * <tr> + * <td><strong>Edm.Double</strong></td> + * <td>{@link EdmType#DOUBLE}</td> + * <td><code>double, Double</code></td> + * <td>A 64-bit double-precision floating point value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Guid</strong></td> + * <td>{@link EdmType#GUID}</td> + * <td><code>UUID</code></td> + * <td>A 128-bit globally unique identifier.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int32</strong></td> + * <td>{@link EdmType#INT32}</td> + * <td><code>int, Integer</code></td> + * <td>A 32-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int64</strong></td> + * <td>{@link EdmType#INT64}</td> + * <td><code>long, Long</code></td> + * <td>A 64-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.String</strong></td> + * <td>{@link EdmType#STRING}</td> + * <td><code>String</code></td> + * <td>A UTF-16-encoded value. String values may be up to 64 KB in size.</td> + * </tr> + * </table> + * <p> + * See the MSDN topic <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179338.aspx">Understanding the + * Table Service Data Model</a> for an overview of tables, entities, and properties as used in the Windows Azure Storage + * service. + * <p> + * For an overview of the available EDM primitive data types and names, see the <a + * href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of the + * <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>. + * <p> + */ public class Entity { private String etag; private Map<String, Property> properties = new HashMap<String, Property>(); + /** + * Gets the ETag value for the entity. This value is used to determine if the table entity has changed since it was + * last read from Windows Azure storage. When modifying an entity, the ETag value may be set to force the operation + * to fail if the ETag does not match the ETag on the server. + * + * @return + * A <code>String</code> containing the ETag for the entity. + */ public String getEtag() { return etag; } + /** + * Sets the ETag value for the entity. This value is used to determine if the table entity has changed since it was + * last read from Windows Azure storage. When modifying an entity, the ETag value may be set to force the operation + * to fail if the ETag does not match the ETag on the server. Set the <em>etag</em>parameter to <code>null</code> to + * force an unconditional operation. + * + * @param etag + * A <code>String</code> containing the ETag for the entity. + */ public Entity setEtag(String etag) { this.etag = etag; return this; } + /** + * Gets the <strong>PartitionKey</strong> value for the entity. + * + * @return + * A <code>String</code> containing the <strong>PartitionKey</strong> value for the entity. + */ public String getPartitionKey() { Property p = getProperty("PartitionKey"); return p == null ? null : (String) p.getValue(); } + /** + * Sets the <strong>PartitionKey</strong> value for the entity. + * <p> + * Tables are partitioned to support load balancing across storage nodes. A table's entities are organized by + * partition. A partition is a consecutive range of entities possessing the same partition key value. The partition + * key is a unique identifier for the partition within a given table, specified by the <strong>PartitionKey</strong> + * property. The partition key forms the first part of an entity's primary key. The partition key may be a string + * value up to 1 KB in size. + * <p> + * You must include the <strong>PartitionKey</strong> property in every insert, update, and delete operation. + * + * @param partitionKey + * A <code>String</code> containing the <strong>PartitionKey</strong> value for the entity. + * @return + * A reference to this {@link Entity} instance. + */ public Entity setPartitionKey(String partitionKey) { setProperty("PartitionKey", null, partitionKey); return this; } + /** + * Gets the <strong>RowKey</strong> value for the entity. + * + * @return + * A <code>String</code> containing the <strong>RowKey</strong> value for the entity. + */ public String getRowKey() { Property p = getProperty("RowKey"); return p == null ? null : (String) p.getValue(); } + /** + * Sets the <strong>RowKey</strong> value for the entity. + * <p> + * The second part of the primary key is the row key, specified by the <strong>RowKey</strong> property. The row key + * is a unique identifier for an entity within a given partition. Together the <strong>PartitionKey</strong> and + * <strong>RowKey</strong> uniquely identify every entity within a table. + * <p> + * The row key is a string value that may be up to 1 KB in size. + * <p> + * You must include the <strong>RowKey</strong> property in every insert, update, and delete operation. + * + * @param rowKey + * A <code>String</code> containing the <strong>RowKey</strong> value for the entity. + * @return + * A reference to this {@link Entity} instance. + */ public Entity setRowKey(String rowKey) { setProperty("RowKey", null, rowKey); return this; } + /** + * Gets the <strong>Timestamp</strong> value for the entity. + * + * @return + * A {@link Date} containing the <strong>Timestamp</strong> value for the entity. + */ public Date getTimestamp() { Property p = getProperty("Timestamp"); return p == null ? null : (Date) p.getValue(); } + /** + * Sets the <strong>Timestamp</strong> value for the entity. + * <p> + * The <strong>Timestamp</strong> property is a value that is maintained on the server side to record the time an + * entity was last modified. The Table service uses the <strong>Timestamp</strong> property internally to provide + * optimistic concurrency. You should treat this property as opaque: It should not be read, nor set on insert or + * update operations (the value will be ignored). + * + * @param timestamp + * A {@link Date} containing the <strong>Timestamp</strong> value for the entity. + * @return + * A reference to this {@link Entity} instance. + */ public Entity setTimestamp(Date timestamp) { setProperty("Timestamp", null, timestamp); return this; } + /** + * Gets the properties collection of name and typed-data pairs in the entity. + * + * @return + * The {@link java.util.HashMap} collection of <code>String</code> property names to {@link Property} + * typed-data value pairs in the entity. + */ public Map<String, Property> getProperties() { return properties; } + /** + * Sets the properties collection of name and typed-data pairs in the entity. + * + * @param properties + * The {@link java.util.HashMap} collection of <code>String</code> property names to {@link Property} + * typed-data value pairs to set in the entity. + * @return + * A reference to this {@link Entity} instance. + */ public Entity setProperties(Map<String, Property> properties) { this.properties = properties; return this; } + /** + * Gets the named property value from the properties collection in the entity. + * + * @param name + * A {@link String} containing the name of the entity property to return. + * @return + * The {@link Property} instance associated with the <em>name</em> parameter value in the entity, + * or <code>null</code> if the name is not in the properties collection. + */ public Property getProperty(String name) { return properties.get(name); } + /** + * Sets the property associated with the specified name in the properties collection in the entity. + * + * @param name + * The {@link String} containing the name to associate with the property value. + * @param property + * The {@link Property} instance to associate with the <em>name</em> parameter value in the entity + * @return + * A reference to this {@link Entity} instance. + */ public Entity setProperty(String name, Property property) { this.properties.put(name, property); return this; } + /** + * Sets the property data type and value associated with the specified name in the properties collection in the + * entity. + * + * @param name + * The {@link String} containing the name to associate with the property value. + * @param edmType + * A {@link String} containing the EDM data type to associate with the property value. This must be one + * of the supported EDM types, defined as string constants in the {@link EdmType} class. + * @param value + * An {@link Object} containing the data value of the property, serializable as the associated EDM data + * type of the property. + * @return + * A reference to this {@link Entity} instance. + */ public Entity setProperty(String name, String edmType, Object value) { setProperty(name, new Property().setEdmType(edmType).setValue(value)); return this; } + /** + * Gets the named property value from the properties collection in the entity cast an an {@link Object}. + * + * @param name + * A {@link String} containing the name of the entity property to return. + * @return + * The value associated with the <em>name</em> parameter in the properties collection in the entity, + * cast as an {@link Object}, or <code>null</code> if the name is not in the properties collection. + */ public Object getPropertyValue(String name) { Property p = getProperty(name); return p == null ? null : p.getValue(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index 1e410d2adfb46..7ec1fd1e13d6d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -14,51 +14,228 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents a table query filter expression, which can be used as an option in a + * {@link TableContract#queryEntities(String, QueryEntitiesOptions)} request. + * <p> + * A filter expression is built of operands consisting of property names, constant values, pre-computed query strings, + * or other filter expressions. Filter expressions may be combined with unary and binary operators to create complex + * expressions. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a> + * topic in MSDN for more information on creating table query filters. + */ public class Filter { + /** + * A static factory method that creates a boolean filter expression that is the logical 'not' of the + * <em>operand</em> parameter. + * + * @param operand + * A {@link Filter} instance containing a boolean filter expression. + * @return + * A {@link UnaryFilter} instance containing a filter expression that is the logical 'not' of the + * <em>operand</em> parameter. + */ public static UnaryFilter not(Filter operand) { return new UnaryFilter("not", operand); } + /** + * A static factory method that creates a boolean filter expression that is the logical 'and' of the <em>left</em> + * and <em>right</em> parameters. + * + * @param left + * A {@link Filter} instance containing a boolean filter expression. + * @param right + * A {@link Filter} instance containing a boolean filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression that is the logical 'and' of the + * <em>left</em> and <em>right</em> parameters. + */ public static BinaryFilter and(Filter left, Filter right) { return new BinaryFilter(left, "and", right); } + /** + * A static factory method that creates a boolean filter expression that is the logical 'or' of the <em>left</em> + * and <em>right</em> parameters. + * + * @param left + * A {@link Filter} instance containing a boolean filter expression. + * @param right + * A {@link Filter} instance containing a boolean filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression that is the logical 'or' of the + * <em>left</em> and <em>right</em> parameters. + */ public static BinaryFilter or(Filter left, Filter right) { return new BinaryFilter(left, "or", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the <em>left</em> and + * <em>right</em> parameters are equal. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the <em>left</em> and + * <em>right</em> parameters are equal. + */ public static BinaryFilter eq(Filter left, Filter right) { return new BinaryFilter(left, "eq", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the <em>left</em> and + * <em>right</em> parameters are not equal. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the <em>left</em> and + * <em>right</em> parameters are not equal. + */ public static BinaryFilter ne(Filter left, Filter right) { return new BinaryFilter(left, "ne", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the value of the + * <em>left</em> parameter is greater than or equal to the value of the <em>right</em> parameter. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the value of the + * <em>left</em> parameter is greater than or equal to the value of the <em>right</em> parameter. + */ public static BinaryFilter ge(Filter left, Filter right) { return new BinaryFilter(left, "ge", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the value of the + * <em>left</em> parameter is greater than the value of the <em>right</em> parameter. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the value of the + * <em>left</em> parameter is greater than the value of the <em>right</em> parameter. + */ public static BinaryFilter gt(Filter left, Filter right) { return new BinaryFilter(left, "gt", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the value of the + * <em>left</em> parameter is less than the value of the <em>right</em> parameter. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the value of the + * <em>left</em> parameter is less than the value of the <em>right</em> parameter. + */ public static BinaryFilter lt(Filter left, Filter right) { return new BinaryFilter(left, "lt", right); } + /** + * A static factory method that creates a boolean filter expression that expresses whether the value of the + * <em>left</em> parameter is less than or equal to the value of the <em>right</em> parameter. + * <p> + * Use this method to create a filter that compares the content of a property with a constant value. + * <p> + * The value must be of the same type as the property for the comparison operation to return valid results. Note + * that it is not possible to compare a property to a dynamic value; one side of the expression must be a constant. + * + * @param left + * A {@link Filter} instance containing a filter expression. + * @param right + * A {@link Filter} instance containing a filter expression. + * @return + * A {@link BinaryFilter} instance containing a filter expression expresses whether the value of the + * <em>left</em> parameter is less than or equal to the value of the <em>right</em> parameter. + */ public static BinaryFilter le(Filter left, Filter right) { return new BinaryFilter(left, "le", right); } + /** + * A static factory method that creates a constant value to use as an operand in a {@link BinaryFilter} expression. + * Case is significant in comparison expressions with constants of type {@link String}. + * + * @param value + * An <code>Object</code> reference to a constant value of a supported type, or <code>null</code>. + * @return + * A {@link ConstantFilter} instance containing the constant value for use in a filter expression. + */ public static ConstantFilter constant(Object value) { return new ConstantFilter(value); } + /** + * A static factory method that creates a property name value to use as an operand in a {@link BinaryFilter} + * expression. Case is significant for the <strong>PartitionKey</strong> and <strong>RowKey</strong> property names. + * + * @param value + * A {@link String} containing the name of a property. + * @return + * A {@link ConstantFilter} instance containing the constant value for use in a filter expression. + */ public static PropertyNameFilter propertyName(String value) { return new PropertyNameFilter(value); } + /** + * A static factory method that creates a table query filter string from the contents of the <em>value</em> + * parameter. + * + * @param value + * A {@link String} containing a table query filter string. + * + * @return + * A {@link QueryStringFilter} instance containing the table query filter string. + */ public static QueryStringFilter queryString(String value) { return new QueryStringFilter(value); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java index 336c436da06c8..982a8d55b028c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java @@ -14,13 +14,40 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request for a single table entity in the storage account returned + * from a Table Service REST API Query Entities operation. This is returned by calls to implementations of + * {@link TableContract#getEntity(String, String, String)} and + * {@link TableContract#getEntity(String, String, String, TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query Entities</a> documentation + * on MSDN for details of the underlying Table Service REST API operation. + */ public class GetEntityResult { private Entity entity; + /** + * Gets the entity returned in the server response to the request. + * + * @return + * The {@link Entity} instance representing the entity returned in the server response to the request. + */ public Entity getEntity() { return entity; } + /** + * Reserved for internal use. Sets the entity value from the properties of the <strong>entry</strong> entity + * returned in the body of the server response. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param entity + * An {@link Entity} instance representing the entity returned in the server response to the request. + */ public void setEntity(Entity entity) { this.entity = entity; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java index e56a2c8f9368d..7f4780fd3c156 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java @@ -14,13 +14,45 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * A wrapper class for the service properties returned in response to Table Service REST API operations. This is + * returned by calls to implementations of {@link TableContract#getServiceProperties()} and + * {@link TableContract#getServiceProperties(TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/hh452238.aspx">Get Table Service Properties</a> + * documentation on MSDN for details of the underlying Table Service REST API operation. + */ public class GetServicePropertiesResult { private ServiceProperties value; + /** + * Gets a {@link ServiceProperties} instance containing the service property values associated with the storage + * account. + * <p> + * Modifying the values in the {@link ServiceProperties} instance returned does not affect the values associated + * with the storage account. To change the values in the storage account, call the + * {@link TableContract#setServiceProperties(ServiceProperties)} or + * {@link TableContract#setServiceProperties(ServiceProperties, TableServiceOptions)} method and pass the modified + * {@link ServiceProperties} instance as a parameter. + * + * @return + * A {@link ServiceProperties} instance containing the property values associated with the storage account. + */ public ServiceProperties getValue() { return value; } + /** + * Reserved for internal use. Sets the value of the {@link ServiceProperties} instance associated with a + * storage service call result. This method is invoked by the API to store service properties returned by + * a call to a REST operation and is not intended for public use. + * + * @param value + * A {@link ServiceProperties} instance containing the property values associated with the storage + * account. + */ public void setValue(ServiceProperties value) { this.value = value; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java index 0e23a0d752c20..a5f3e843966ba 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java @@ -14,13 +14,39 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request for a single table entry in the list of tables in the storage account returned + * from a Table Service REST API Query Tables operation. This is returned by calls to implementations of + * {@link TableContract#getTable(String)} and {@link TableContract#getTable(String, TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query Tables</a> documentation + * on MSDN for details of the underlying Table Service REST API operation. + */ public class GetTableResult { private TableEntry tableEntry; + /** + * Gets the table entry returned in the server response. + * + * @return + * A {@link TableEntry} instance representing the table entry returned in the server response. + */ public TableEntry getTableEntry() { return tableEntry; } + /** + * Reserved for internal use. Sets the table entry value from the <strong>TableName</strong> entity in the + * properties of the <strong>entry</strong> entity returned in the body of the server response. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param tableEntry + * A {@link TableEntry} instance representing the table entry returned in the server response. + */ public void setTableEntry(TableEntry tableEntry) { this.tableEntry = tableEntry; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java index eca0203954967..1c786f9b4b084 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java @@ -14,13 +14,40 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request to insert a single table entity in the storage account returned + * from a Table Service REST API Insert Entity operation. This is returned by calls to implementations of + * {@link TableContract#insertEntity(String, Entity)} and + * {@link TableContract#insertEntity(String, Entity, TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179433.aspx">Insert Entity</a> documentation + * on MSDN for details of the underlying Table Service REST API operation. + */ public class InsertEntityResult { private Entity entity; + /** + * Gets the entity returned in the server response to the request. + * + * @return + * The {@link Entity} instance representing the entity returned in the server response to the request. + */ public Entity getEntity() { return entity; } + /** + * Reserved for internal use. Sets the entity value from the properties of the <strong>entry</strong> entity + * returned in the body of the server response. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param entity + * An {@link Entity} instance representing the entity returned in the server response to the request. + */ public void setEntity(Entity entity) { this.entity = entity; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java index 197584f920e6d..949e90a91330b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java @@ -14,23 +14,132 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a table entity property value as a data-type and value pair. + * <p> + * The following table shows the supported property data types in Windows Azure storage and the corresponding Java types + * when deserialized. + * <TABLE BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0"> + * <TR BGCOLOR="#EEEEFF" CLASS="TableSubHeadingColor"> + * <th>Storage Type</th> + * <th>EdmType Value</th> + * <th>Java Type</th> + * <th>Description</th> + * </tr> + * <tr> + * <td><strong>Edm.Binary</strong></td> + * <td>{@link EdmType#BINARY}</td> + * <td><code>byte[], Byte[]</code></td> + * <td>An array of bytes up to 64 KB in size.</td> + * </tr> + * <tr> + * <td><strong>Edm.Boolean</strong></td> + * <td>{@link EdmType#BOOLEAN}</td> + * <td><code>boolean, Boolean</code></td> + * <td>A Boolean value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Byte</strong></td> + * <td>{@link EdmType#BYTE}</td> + * <td><code>byte, Byte</code></td> + * <td>An 8-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.DateTime</strong></td> + * <td>{@link EdmType#DATE_TIME}</td> + * <td><code>Date</code></td> + * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, + * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> + * </tr> + * <tr> + * <td><strong>Edm.Double</strong></td> + * <td>{@link EdmType#DOUBLE}</td> + * <td><code>double, Double</code></td> + * <td>A 64-bit double-precision floating point value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Guid</strong></td> + * <td>{@link EdmType#GUID}</td> + * <td><code>UUID</code></td> + * <td>A 128-bit globally unique identifier.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int32</strong></td> + * <td>{@link EdmType#INT32}</td> + * <td><code>int, Integer</code></td> + * <td>A 32-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.Int64</strong></td> + * <td>{@link EdmType#INT64}</td> + * <td><code>long, Long</code></td> + * <td>A 64-bit integer value.</td> + * </tr> + * <tr> + * <td><strong>Edm.String</strong></td> + * <td>{@link EdmType#STRING}</td> + * <td><code>String</code></td> + * <td>A UTF-16-encoded value. String values may be up to 64 KB in size.</td> + * </tr> + * </table> + * <p> + * See the MSDN topic <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179338.aspx">Understanding the + * Table Service Data Model</a> for an overview of tables, entities, and properties as used in the Windows Azure Storage + * service. + * <p> + * For an overview of the available EDM primitive data types and names, see the <a + * href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of the + * <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>. + */ public class Property { private String edmType; private Object value; + /** + * Gets the EDM data type of the property. + * + * @return + * A <code>String</code> containing the EDM data type of the property. + */ public String getEdmType() { return edmType; } + /** + * Sets the EDM data type of the property. The <em>edmType</em> parameter must be set to one of the string constants + * defined in the {@link EdmType} class. + * + * @param edmType + * A {@link String} containing the EDM data type to associate with the property value. This must be one + * of the supported EDM types, defined as string constants in the {@link EdmType} class. + * @return + * A reference to this {@link Property} instance. + */ public Property setEdmType(String edmType) { this.edmType = edmType; return this; } + /** + * Gets the data value of the property. + * + * @return + * An {@link Object} containing the data value of the property. + */ public Object getValue() { return value; } + /** + * Sets the data value of the property. The <em>value</em> parameter must contain an {@link Object} serializable as + * the associated EDM data type of the property. + * + * @param value + * An {@link Object} containing the data value of the property, serializable as the associated EDM data + * type of the property. + * @return + * A reference to this {@link Property} instance. + */ public Property setValue(Object value) { this.value = value; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java index 49cb129d86e03..c21978954c657 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java @@ -14,13 +14,44 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a property name used as a filter parameter in a table query request. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link PropertyNameFilter}, rather than + * constructing one directly. + * <p> + * Use this class to pass a property name as a filter parameter. Case is significant for the + * <strong>PartitionKey</strong> and <strong>RowKey</strong> property names in a filter. A {@link PropertyNameFilter} + * may be combined in a {@link BinaryFilter} with a comparison operator and a {@link ConstantFilter} to limit query + * results to properties with values that match the {@link ConstantFilter} value. The table service does not support + * wildcard queries, but you can perform prefix matching by using comparison operators on the desired prefix created as + * a {@link String} in a {@link ConstantFilter} instance. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a> + * topic in MSDN for more information on creating table query filter strings. + */ public class PropertyNameFilter extends Filter { private final String propertyName; + /** + * Creates a property name table query filter from the <em>propertyName</em> parameter. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link PropertyNameFilter}, rather than + * constructing one directly. + * + * @param propertyName + * A {@link String} containing the property name to use as a filter parameter in a table query request. + */ public PropertyNameFilter(String propertyName) { this.propertyName = propertyName; } + /** + * Gets the property name set in this {@link PropertyNameFilter} instance. + * + * @return + * The {@link String} containing the property name to use as a filter parameter in a table query request. + */ public String getPropertyName() { return propertyName; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index e5d7a7c511513..740c0b9cb1db0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -17,6 +17,15 @@ import java.util.ArrayList; import java.util.List; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the options that may be set on a {@link TableContract#queryEntities(String, QueryEntitiesOptions) + * queryEntities} request. These options include the next partition key and next row key continuation tokens to use to + * resume the query entities request from, a collection of the property names to include in the entities returned in the + * server response, a filter to limit results to entities with certain property values, and a top count to limit the + * response to that number of the first matching results. + */ public class QueryEntitiesOptions extends TableServiceOptions { private List<String> selectFields = new ArrayList<String>(); @@ -28,74 +37,235 @@ public class QueryEntitiesOptions extends TableServiceOptions { public String nextPartitionKey; public String nextRowKey; + /** + * Gets the next partition key continuation token set in this {@link QueryEntitiesOptions} instance. + * + * @return + * A {@link String} containing the next partition key continuation token to use to resume a query entities + * request with. + */ public String getNextPartitionKey() { return nextPartitionKey; } + /** + * Sets the next partition key continuation token to resume a query entities request with. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryEntitiesResult#getNextPartitionKey()} and {@link QueryEntitiesResult#getNextRowKey()} methods + * on the result of a query entities request to determine if there are more results to retrieve. If so, set the + * values returned on a {@link QueryEntitiesOptions} instance with the {@link #setNextPartitionKey(String)} and + * {@link #setNextRowKey(String)} methods, and resume the query with another call to + * {@link TableContract#queryEntities(String, QueryEntitiesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * <p> + * This value only affects calls made on methods where this {@link QueryEntitiesOptions} instance is passed as a + * parameter. + * + * @param nextPartitionKey + * A {@link String} containing the next partition key continuation token to use to resume a query + * entities request with. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setNextPartitionKey(String nextPartitionKey) { this.nextPartitionKey = nextPartitionKey; return this; } + /** + * Gets the next row key continuation token set in this {@link QueryEntitiesOptions} instance. + * + * @return + * A {@link String} containing the next row key continuation token to use to resume a query entities request + * with. + */ public String getNextRowKey() { return nextRowKey; } + /** + * Sets the next row key continuation token to resume a query entities request with. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryEntitiesResult#getNextPartitionKey()} and {@link QueryEntitiesResult#getNextRowKey()} methods + * on the result of a query entities request to determine if there are more results to retrieve. If so, set the + * values returned on a {@link QueryEntitiesOptions} instance with the {@link #setNextPartitionKey(String)} and + * {@link #setNextRowKey(String)} methods, and resume the query with another call to + * {@link TableContract#queryEntities(String, QueryEntitiesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * <p> + * This value only affects calls made on methods where this {@link QueryEntitiesOptions} instance is passed as a + * parameter. + * + * @param nextRowKey + * A {@link String} containing the next row key continuation token to use to resume a query + * entities request with. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setNextRowKey(String nextRowKey) { this.nextRowKey = nextRowKey; return this; } + /** + * Gets the collection of properties to include in the entities returned in the server response set in this + * {@link QueryEntitiesOptions} instance. + * + * @return + * The {@link java.util.List} of {@link String} property names to include in the entities returned in the + * server response. + */ public List<String> getSelectFields() { return selectFields; } + /** + * Sets the collection of properties to include in the entities returned in the server response. + * + * @param selectFields + * A {@link java.util.List} of {@link String} property names to include in the entities returned in the + * server response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setSelectFields(List<String> selectFields) { this.selectFields = selectFields; return this; } + /** + * Adds a property name to the collection of properties to include in the entities returned in the server response. + * + * @param selectField + * A {@link String} property name to include in the entities returned in the server response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions addSelectField(String selectField) { this.selectFields.add(selectField); return this; } + /** + * Reserved for future use. Gets the from option value set in this {@link QueryEntitiesOptions} instance. + * + * @return + * A {@link String} containing the from option value set. + */ public String getFrom() { return from; } + /** + * Reserved for future use. Sets the from option value. + * + * @param from + * A {@link String} containing the from option value to set. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setFrom(String from) { this.from = from; return this; } + /** + * Gets the table query filter set in this {@link QueryEntitiesOptions} instance. + * + * @return + * A {@link Filter} containing the table query filter to apply on the server to limit the entities returned + * in the response. + */ public Filter getFilter() { return filter; } + /** + * Sets the table query filter to apply on the server to limit the entities returned in the response. + * + * @param filter + * A {@link Filter} containing the table query filter to apply on the server to limit the entities + * returned in the response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setFilter(Filter filter) { this.filter = filter; return this; } + /** + * Reserved for future use. Gets the list of property names to use to order the results in the server response set + * in this {@link QueryEntitiesOptions} instance. + * + * @return + * The {@link java.util.List} of {@link String} property names to use to order the results in the server + * response. + */ public List<String> getOrderByFields() { return orderByFields; } + /** + * Reserved for future use. Sets the list of property names to use to order the results in the server response. + * + * @param orderByFields + * A {@link java.util.List} of {@link String} property names to use to order the results in the server + * response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setOrderByFields(List<String> orderByFields) { this.orderByFields = orderByFields; return this; } + /** + * Reserved for future use. Adds a property name to the list of property names to use to order the results in the + * server response. + * + * @param orderByFields + * A {@link String} containing a property name to add to the list to use to order the results in the + * server response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions addOrderByField(String orderByField) { this.orderByFields.add(orderByField); return this; } + /** + * Gets the number of entities to return in the server response set in this {@link QueryEntitiesOptions} instance. + * + * @return + * The number of entities to return in the server response. + */ public Integer getTop() { return top; } + /** + * Sets the number of entities to return in the server response. The first results in order by partition key and row + * key will be returned. + * + * @param top + * The number of entities to return in the server response. + * @return + * A reference to this {@link QueryEntitiesOptions} instance. + */ public QueryEntitiesOptions setTop(Integer top) { this.top = top; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java index 9756661eef96b..efe1da871358a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java @@ -17,31 +17,131 @@ import java.util.ArrayList; import java.util.List; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request for a list of entities in the storage account returned from a Table Service REST + * API Query Tables operation. This is returned by calls to implementations of + * {@link TableContract#queryEntities(String)} and {@link TableContract#queryEntities(String, QueryEntitiesOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179421.aspx">Query Entities</a> documentation + * on MSDN for details of the underlying Table Service REST API operation. + */ public class QueryEntitiesResult { private String nextPartitionKey; private String nextRowKey; private List<Entity> entities = new ArrayList<Entity>(); + /** + * Gets the list of entities returned in the server response to the query entities request. + * + * @return + * A {@link java.util.List} of {@link Entity} instances representing the entities returned in the server + * response. + */ public List<Entity> getEntities() { return entities; } + /** + * Reserved for internal use. Sets the list of entities returned in the server response to the query entities + * request. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param entities + * A {@link java.util.List} of {@link Entity} instances representing the entities returned in the server + * response. + */ public void setEntities(List<Entity> entities) { this.entities = entities; } + /** + * Gets the next partition key continuation token to resume the query entities request with, if any is returned. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryEntitiesResult#getNextPartitionKey()} and {@link QueryEntitiesResult#getNextRowKey()} methods + * on the result of a query entities request to determine if there are more results to retrieve. If so, set the + * values returned on a {@link QueryEntitiesOptions} instance with the {@link #setNextPartitionKey(String)} and + * {@link #setNextRowKey(String)} methods, and resume the query with another call to + * {@link TableContract#queryEntities(String, QueryEntitiesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * + * @return + * A {@link String} containing the next partition key continuation token to use to resume the query entities + * request with, or <code>null</code> if no token was returned. + */ public String getNextPartitionKey() { return nextPartitionKey; } + /** + * Reserved for internal use. Sets the next partition key continuation token to resume a query entities request with + * from the <strong>x-ms-continuation-NextRowKey</strong> header returned in the response from the server. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param nextPartitionKey + * A {@link String} containing the next partition key continuation token to use to resume a query + * entities request with. + */ public void setNextPartitionKey(String nextPartitionKey) { this.nextPartitionKey = nextPartitionKey; } + /** + * Gets the next row key continuation token to resume the query tables request with, if any is returned. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryEntitiesResult#getNextPartitionKey()} and {@link QueryEntitiesResult#getNextRowKey()} methods + * on the result of a query entities request to determine if there are more results to retrieve. If so, set the + * values returned on a {@link QueryEntitiesOptions} instance with the {@link #setNextPartitionKey(String)} and + * {@link #setNextRowKey(String)} methods, and resume the query with another call to + * {@link TableContract#queryEntities(String, QueryEntitiesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * + * @return + * A {@link String} containing the next row key continuation token to use to resume the query tables + * request with, or <code>null</code> if no token was returned. + */ public String getNextRowKey() { return nextRowKey; } + /** + * Reserved for internal use. Sets the next row key continuation token to resume a query entities request with from + * the <strong>x-ms-continuation-NextRowKey</strong> header returned in the response from the server. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param nextRowKey + * A {@link String} containing the next row key continuation token to use to resume a query entities + * request with. + */ public void setNextRowKey(String nextRowKey) { this.nextRowKey = nextRowKey; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java index 77341e2477c57..84db88c276ae4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java @@ -14,13 +14,55 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a table query filter string passed directly as a filter parameter in a table query request. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link QueryStringFilter}, rather than + * constructing one directly. + * <p> + * Use this class to pass a literal query filter string without interpretation as a filter parameter. For example, if + * you have a created a query filter expression independently, this class may be used to add it to a filter expression. + * <p> + * The following characters must be URL-encoded if they are to be used in a query string: + * <ul> + * <li>Forward slash (/)</li> + * <li>Question mark (?)</li> + * <li>Colon (:)</li> + * <li>'At' symbol (@)</li> + * <li>Ampersand (&)</li> + * <li>Equals sign (=)</li> + * <li>Plus sign (+)</li> + * <li>Comma (,)</li> + * <li>Dollar sign ($)</li> + * </ul> + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a> + * topic in MSDN for more information on creating table query filter strings. + */ public class QueryStringFilter extends Filter { private final String queryString; + /** + * Creates a table query filter from the <em>queryString</em> parameter. + * <p> + * Use the static factory method in the {@link Filter} class to create a {@link QueryStringFilter}, rather than + * constructing one directly. + * + * @param queryString + * A {@link String} containing a table query filter to pass directly as a filter parameter in a table + * query request. + */ public QueryStringFilter(String queryString) { this.queryString = queryString; } + /** + * Gets the table query filter string set in this {@link QueryStringFilter} instance. + * + * @return + * A {@link String} containing a table query filter to pass directly as a filter parameter in a table + * query request. + */ public String getQueryString() { return queryString; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java index d72c580a402f4..e7c89aeb759ef 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -14,33 +14,106 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the options that may be set on a {@link TableContract#queryTables(QueryTablesOptions) queryTables} + * request. These options include a filter to limit results to tables with certain properties, the next table name + * continuation token to use to resume the query tables request from, and a prefix string to match table names with. + */ public class QueryTablesOptions extends TableServiceOptions { private Filter filter; private String nextTableName; private String prefix; + /** + * Gets the filter to use to limit table entries returned set in this {@link TableServiceOptions} instance. + * + * @return + * A {@link Filter} instance containing the filter options to use. + */ public Filter getFilter() { return filter; } + /** + * Sets the filter to use to limit table entries returned by the request to those that match the filter properties. + * <p> + * This value only affects calls made on methods where this {@link QueryTablesOptions} instance is passed as a + * parameter. + * + * @param filter + * A {@link Filter} instance containing the filter options to use. + * @return + * A reference to this {@link QueryTablesOptions} instance. + */ public QueryTablesOptions setFilter(Filter filter) { this.filter = filter; return this; } + /** + * Gets the next table name continuation token set in this {@link TableServiceOptions} instance. + * + * @return + * A {@link String} containing the next table name continuation token to use to resume a query tables + * request with. + */ public String getNextTableName() { return nextTableName; } + /** + * Sets the next table name continuation token to resume a query tables request with. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryTablesResult#getNextTableName()} method on the result of a query tables request to determine + * if there are more results to retrieve. If so, pass the value returned as the <em>nextTableName</em> parameter to + * this method to set the next table name continuation token option, and resume the query with another call to + * {@link TableContract#queryTables(QueryTablesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * <p> + * This value only affects calls made on methods where this {@link QueryTablesOptions} instance is passed as a + * parameter. + * + * @param nextTableName + * A {@link String} containing the next table name continuation token to use to resume a query tables + * request with. + * @return + * A reference to this {@link QueryTablesOptions} instance. + */ public QueryTablesOptions setNextTableName(String nextTableName) { this.nextTableName = nextTableName; return this; } + /** + * Gets the prefix filter associated with this {@link QueryTablesOptions} instance. This value is used to return + * only tables from the storage account with names that begin with the prefix in the response to a query tables + * request. + * + * @return + * A {@link String} containing the prefix used to filter the table names returned, if any. + */ public String getPrefix() { return prefix; } + /** + * Sets the optional table name prefix filter value to use in a request. If this value is set, the server will + * return only table entries with names that match the prefix value in the response. + * <p> + * The <em>prefix</em> value only affects calls made on methods where this {@link QueryTablesOptions} instance is + * passed as a parameter. + * + * @param prefix + * A {@link String} containing a prefix to use to filter the table names returned. + */ public QueryTablesOptions setPrefix(String prefix) { this.prefix = prefix; return this; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java index 9c6158382dba3..2b8e98e62baa5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -16,22 +16,85 @@ import java.util.List; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request for a list of tables in the storage account returned + * from a Table Service REST API Query Tables operation. This is returned by calls to implementations of + * {@link TableContract#queryTables()} and {@link TableContract#queryTables(QueryTablesOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query Tables</a> documentation + * on MSDN for details of the underlying Table Service REST API operation. + */ public class QueryTablesResult { private String nextTableName; private List<TableEntry> tables; + /** + * Gets the next table name continuation token to resume the query tables request with, if any is returned. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * Use the {@link QueryTablesResult#getNextTableName()} method on the result of a query tables request to determine + * if there are more results to retrieve. If so, pass the value returned as the <em>nextTableName</em> parameter to + * this method to set the next table name continuation token option, and resume the query with another call to + * {@link TableContract#queryTables(QueryTablesOptions)} to get the next set of results. + * <p> + * It is possible for a query to return no results but to still return a continuation token. + * + * @return + * A {@link String} containing the next table name continuation token to use to resume the query tables + * request with, or <code>null</code> if no token was returned. + */ public String getNextTableName() { return nextTableName; } + /** + * Reserved for internal use. Sets the next table name continuation token to resume a query tables request with from + * the <strong>x-ms-continuation-NextTableName</strong> header returned in the response from the server. + * <p> + * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum + * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five + * seconds, or if the query crosses the partition boundary, the response includes values which provide the client + * with continuation tokens to use in order to resume the query at the next item in the result set. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param nextTableName + * A {@link String} containing the next table name continuation token to use to resume a query tables + * request with. + * @return + * A reference to this {@link QueryTablesOptions} instance. + */ public void setNextTableName(String nextTableName) { this.nextTableName = nextTableName; } + /** + * Gets the list of table entries returned in the server response. + * + * @return + * A {@link List} of {@link TableEntry} instances containing the table entries returned in the response. + */ public List<TableEntry> getTables() { return tables; } + /** + * Reserved for internal use. Sets the list of table entries from each <strong>TableName</strong> entity in the + * properties of the <strong>entry</strong> entities returned in the body of the server response. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param tables + * A {@link List} of {@link TableEntry} instances containing the table entries returned in the response. + */ public void setTables(List<TableEntry> tables) { this.tables = tables; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java index 01a18543864b1..6f8a4f11c0c0b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java @@ -17,29 +17,65 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the Storage service properties that can be set on a storage account, including Windows Azure Storage + * Analytics. This class is used by the {@link TableContract#getServiceProperties()} method to return the service + * property values set on the storage account, and by the {@link TableContract#setServiceProperties(ServiceProperties)} + * and {@link TableContract#setServiceProperties(ServiceProperties, TableServiceOptions)} methods to set the values of + * the service properties. + */ @XmlRootElement(name = "StorageServiceProperties") public class ServiceProperties { private Logging logging = new Logging(); private Metrics metrics = new Metrics(); + /** + * Gets the value of the logging options on the storage account. + * + * @return + * A {@link Logging} instance containing the logging options. + */ @XmlElement(name = "Logging") public Logging getLogging() { return logging; } + /** + * Sets the value of the logging options on the storage account. + * + * @param logging + * A {@link Logging} instance containing the logging options. + */ public void setLogging(Logging logging) { this.logging = logging; } + /** + * Gets the value of the metrics options on the storage account. + * + * @return + * A {@link Metrics} instance containing the metrics options. + */ @XmlElement(name = "Metrics") public Metrics getMetrics() { return metrics; } + /** + * Sets the value of the metrics options on the storage account. + * + * @param metrics + * A {@link Metrics} instance containing the metrics options. + */ public void setMetrics(Metrics metrics) { this.metrics = metrics; } + /** + * Represents the logging options that can be set on a storage account. + */ public static class Logging { private String version; private boolean delete; @@ -47,113 +83,258 @@ public static class Logging { private boolean write; private RetentionPolicy retentionPolicy; + /** + * Gets the retention policy for logging data set on the storage account. + * + * @return + * The {@link RetentionPolicy} set on the storage account. + */ @XmlElement(name = "RetentionPolicy") public RetentionPolicy getRetentionPolicy() { return retentionPolicy; } + /** + * Sets the retention policy to use for logging data on the storage account. + * + * @param retentionPolicy + * The {@link RetentionPolicy} to set on the storage account. + */ public void setRetentionPolicy(RetentionPolicy retentionPolicy) { this.retentionPolicy = retentionPolicy; } + /** + * Gets a flag indicating whether all write requests are logged. + * + * @return + * A flag value of <code>true</code> if all write operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Write") public boolean isWrite() { return write; } + /** + * Sets a flag indicating whether all write requests should be logged. + * + * @param write + * Set a flag value of <code>true</code> to log all write operations; otherwise, <code>false</code>. + */ public void setWrite(boolean write) { this.write = write; } + /** + * Gets a flag indicating whether all read requests are logged. + * + * @return + * A flag value of <code>true</code> if all read operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Read") public boolean isRead() { return read; } + /** + * Sets a flag indicating whether all read requests should be logged. + * + * @param read + * Set a flag value of <code>true</code> to log all read operations; otherwise, <code>false</code>. + */ public void setRead(boolean read) { this.read = read; } + /** + * Gets a flag indicating whether all delete requests are logged. + * + * @return + * A flag value of <code>true</code> if all delete operations are logged; otherwise, <code>false</code>. + */ @XmlElement(name = "Delete") public boolean isDelete() { return delete; } + /** + * Sets a flag indicating whether all delete requests should be logged. + * + * @param delete + * Set a flag value of <code>true</code> to log all delete operations; otherwise, <code>false</code>. + */ public void setDelete(boolean delete) { this.delete = delete; } + /** + * Gets the version of logging configured on the storage account. + * + * @return + * A {@link String} containing the version of logging configured on the storage account. + */ @XmlElement(name = "Version") public String getVersion() { return version; } + /** + * Sets the version of logging configured on the storage account. + * + * @param version + * A {@link String} containing the version of logging configured on the storage account. + */ public void setVersion(String version) { this.version = version; } } + /** + * Represents the metrics options that can be set on a storage account. + */ public static class Metrics { private String version; private boolean enabled; private Boolean includeAPIs; private RetentionPolicy retentionPolicy; + /** + * Gets the retention policy for metrics data set on the storage account. + * + * @return + * The {@link RetentionPolicy} set on the storage account. + */ @XmlElement(name = "RetentionPolicy") public RetentionPolicy getRetentionPolicy() { return retentionPolicy; } + /** + * Sets the retention policy to use for metrics data on the storage account. + * + * @param retentionPolicy + * The {@link RetentionPolicy} to set on the storage account. + */ public void setRetentionPolicy(RetentionPolicy retentionPolicy) { this.retentionPolicy = retentionPolicy; } + /** + * Gets a flag indicating whether metrics generates summary statistics for called API operations. + * + * @return + * A flag value of <code>true</code> if metrics generates summary statistics for called API operations; + * otherwise, <code>false</code>. + */ @XmlElement(name = "IncludeAPIs") public Boolean isIncludeAPIs() { return includeAPIs; } + /** + * Sets a flag indicating whether metrics should generate summary statistics for called API operations. This + * flag is optional if metrics is not enabled. + * + * @param includeAPIs + * Set a flag value of <code>true</code> to generate summary statistics for called API operations; + * otherwise, <code>false</code>. + */ public void setIncludeAPIs(Boolean includeAPIs) { this.includeAPIs = includeAPIs; } + /** + * Gets a flag indicating whether metrics is enabled for the storage account. + * + * @return + * A flag value of <code>true</code> if metrics is enabled for the storage account; otherwise, + * <code>false</code>. + */ @XmlElement(name = "Enabled") public boolean isEnabled() { return enabled; } + /** + * Sets a flag indicating whether to enable metrics for the storage account. + * + * @param enabled + * Set a flag value of <code>true</code> to enable metrics for the storage account; otherwise, + * <code>false</code>. + */ public void setEnabled(boolean enabled) { this.enabled = enabled; } + /** + * Gets the version of Storage Analytics configured on the storage account. + * + * @return + * A {@link String} containing the version of Storage Analytics configured on the storage account. + */ @XmlElement(name = "Version") public String getVersion() { return version; } + /** + * Sets the version of Storage Analytics configured on the storage account. + * + * @param version + * A {@link String} containing the version of Storage Analytics configured on the storage account. + */ public void setVersion(String version) { this.version = version; } } + /** + * Represents the optional retention policy that can be applied to logging or metrics on the storage account. + */ public static class RetentionPolicy { private boolean enabled; private Integer days; // nullable, because optional if "enabled" is false + /** + * Gets the number of days that metrics or logging data should be retained, if logging is enabled. + * + * @return + * The number of days to retain logging or metrics data if logging is enabled, or <code>null</code>. + */ @XmlElement(name = "Days") public Integer getDays() { return days; } + /** + * Sets the number of days that metrics or logging data should be retained. The minimum value you can specify is + * 1; the largest value is 365 (one year). This value must be specified even if the enabled flag is set + * to <code>false</code>. + * + * @param days + * The number of days to retain logging or metrics data. + */ public void setDays(Integer days) { this.days = days; } + /** + * Gets a flag indicating whether a retention policy is enabled. + * + * @return + * A flag value of <code>true</code> if a retention policy is enabled; otherwise, <code>false</code>. + */ @XmlElement(name = "Enabled") public boolean isEnabled() { return enabled; } + /** + * Sets a flag indicating whether to enable a retention policy. + * + * @param enabled + * Set a flag value of <code>true</code> to enable a retention policy; otherwise, <code>false</code>. + */ public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java index 2e520bc36db17..cc65eff4e1d40 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java @@ -14,13 +14,28 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a single table entry in the list of tables in the storage account. + */ public class TableEntry { private String name; + /** + * Gets the name of the table. + * + * @return + * A {@link String} containing the name of the table. + */ public String getName() { return name; } + /** + * Sets the name of the table. + * + * @param name + * A {@link String} containing the name of the table. + */ public void setName(String name) { this.name = name; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java index c4a733a6e3a6b..0eb1b91b27aa3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java @@ -14,5 +14,8 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * This class is a base class for options that may be set on certain Table service requests. + */ public class TableServiceOptions { } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java index 99aa90409830b..190437d27b530 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java @@ -14,19 +14,47 @@ */ package com.microsoft.windowsazure.services.table.models; +/** + * Represents a table query filter expression consisting of a unary logical operator and a boolean filter expression. + * Use the static factory methods in the {@link Filter} class to create <code>UnaryFilter</code> instances, rather + * than constructing them directly. + */ public class UnaryFilter extends Filter { private final String operator; private final Filter operand; + /** + * Creates a <code>UnaryFilter</code> expression from a unary logical operator and a boolean {@link Filter}. + * <p> + * Use the static factory methods in the {@link Filter} class to create <code>UnaryFilter</code> instances, rather + * than constructing them directly. + * + * @param operator + * A {@link String} containing the unary logical operator to use in the expression. + * @param operand + * The boolean {@link Filter} to use as the operand of the expression. + */ public UnaryFilter(String operator, Filter operand) { this.operator = operator; this.operand = operand; } + /** + * Gets the unary logical operator to use in the <code>UnaryFilter</code> expression. + * + * @return + * A {@link String} containing the unary logical operator to use in the expression. + */ public String getOperator() { return operator; } + /** + * Gets the boolean {@link Filter} to use as the operand of the <code>UnaryFilter</code> expression. + * + * @return + * A {@link Filter} to use as the operand of the expression. + */ public Filter getOperand() { return operand; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java index dc18aa98996ec..0e21304ccaa97 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java @@ -14,13 +14,46 @@ */ package com.microsoft.windowsazure.services.table.models; +import com.microsoft.windowsazure.services.table.TableContract; + +/** + * Represents the response to a request to modify a single table entity in the storage account returned + * from a Table Service REST API Update Entity, Merge Entity, Insert or Replace Entity, or Insert or Merge Entity + * operation. This is returned by calls to implementations of {@link TableContract#mergeEntity(String, Entity)}, + * {@link TableContract#mergeEntity(String, Entity, TableServiceOptions)}, + * {@link TableContract#insertOrMergeEntity(String, Entity)}, + * {@link TableContract#insertOrMergeEntity(String, Entity, TableServiceOptions)}, + * {@link TableContract#insertOrReplaceEntity(String, Entity)}, + * {@link TableContract#insertOrReplaceEntity(String, Entity, TableServiceOptions)}, + * {@link TableContract#updateEntity(String, Entity)} and + * {@link TableContract#updateEntity(String, Entity, TableServiceOptions)}. + * <p> + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179427.aspx">Update Entity</a> documentation + * on MSDN for details of the underlying Table Service REST API operations. + */ public class UpdateEntityResult { private String etag; + /** + * Gets the ETag value of the modified entity returned in the server response. + * + * @return + * A {@link String} containing the ETag value of the modified entity. + */ public String getEtag() { return etag; } + /** + * Reserved for internal use. Sets the ETag value of the modified entity from the <strong>ETag</strong> header value + * in the server response. + * <p> + * This method is invoked by the API to set the value from the Table Service REST API operation response returned by + * the server. + * + * @param etag + * A {@link String} containing the ETag value of the modified entity. + */ public void setEtag(String etag) { this.etag = etag; } From 63a6effd283823f4055c4a6a52d04d97db927ad5 Mon Sep 17 00:00:00 2001 From: Colin Robertson <a-colinr@microsoft.com> Date: Wed, 9 May 2012 13:29:40 -0700 Subject: [PATCH 72/76] Add package.html files, add Javadocs to Table, copy edit for Blob and Table javadocs. --- .../services/blob/BlobContract.java | 9 ++-- .../services/blob/client/package.html | 5 ++ .../services/blob/implementation/package.html | 5 ++ .../models/AccessConditionHeaderType.java | 24 +++++----- .../blob/models/CreateBlobSnapshotResult.java | 8 ++-- .../blob/models/GetBlobMetadataResult.java | 10 ++-- .../blob/models/GetBlobPropertiesResult.java | 4 +- .../services/blob/models/GetBlobResult.java | 4 +- .../models/GetContainerPropertiesResult.java | 8 ++-- .../blob/models/ListBlobBlocksResult.java | 8 ++-- .../blob/models/ListBlobRegionsResult.java | 8 ++-- .../blob/models/ListBlobsOptions.java | 4 +- .../services/blob/models/ListBlobsResult.java | 4 +- .../blob/models/ListContainersOptions.java | 4 +- .../blob/models/ListContainersResult.java | 16 +++---- .../services/blob/models/PageRange.java | 10 ++-- .../services/blob/models/package.html | 5 ++ .../services/queue/client/package.html | 5 ++ .../queue/implementation/package.html | 5 ++ .../services/table/EdmValueConverter.java | 31 ++++++++++++ .../services/table/TableConfiguration.java | 21 +++++++++ .../services/table/TableService.java | 47 +++++++++++++++++++ .../services/table/client/package.html | 5 ++ .../table/implementation/package.html | 5 ++ .../table/models/BatchOperations.java | 10 ++-- .../services/table/models/BatchResult.java | 6 +-- .../services/table/models/ConstantFilter.java | 2 +- .../services/table/models/EdmType.java | 8 +--- .../services/table/models/Entity.java | 8 +--- .../services/table/models/Filter.java | 4 +- .../services/table/models/Property.java | 8 +--- .../table/models/PropertyNameFilter.java | 16 ++++--- .../table/models/QueryEntitiesOptions.java | 2 +- .../table/models/QueryEntitiesResult.java | 4 +- .../table/models/QueryTablesResult.java | 4 +- .../table/models/UpdateEntityResult.java | 2 +- .../services/table/models/package.html | 5 ++ .../windowsazure/services/table/package.html | 5 ++ 38 files changed, 235 insertions(+), 104 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/package.html create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/package.html diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java index c6d2ec0d44332..af7db3065b418 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/BlobContract.java @@ -690,11 +690,10 @@ void createBlobBlock(String container, String blob, String blockId, InputStream * You can call this method to update a blob by uploading only those blocks that have changed, then committing the * new and existing blocks together. You can do this with the <em>blockList</em> parameter by specifying whether to * commit a block from the committed block list or from the uncommitted block list, or to commit the most recently - * uploaded version of the block, whichever list it may belong to. Use the - * {@link BlobContract#CommitBlobBlocksOptions options} parameter to optionally specify the server timeout for the - * operation, the MIME content type and content encoding for the blob, the content language, the MD5 hash, a cache - * control value, blob metadata, the lease ID if the blob has an active lease, and any access conditions for the - * operation. + * uploaded version of the block, whichever list it may belong to. Use the {@link CommitBlobBlocksOptions options} + * parameter to optionally specify the server timeout for the operation, the MIME content type and content encoding + * for the blob, the content language, the MD5 hash, a cache control value, blob metadata, the lease ID if the blob + * has an active lease, and any access conditions for the operation. * <p> * In order to be written as part of a blob, each block in the list must have been successfully written to the * server with a call to {@link BlobContract#createBlobBlock(String, String, String, InputStream)} or diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/package.html new file mode 100644 index 0000000000000..cbf5ee75f3777 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the integrated blob service client classes and implementation. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/package.html new file mode 100644 index 0000000000000..d44ec4f38bcea --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the implementation of the blob service classes and utilities. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessConditionHeaderType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessConditionHeaderType.java index 30045ee84b981..34966982a9da4 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessConditionHeaderType.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/AccessConditionHeaderType.java @@ -2,15 +2,15 @@ * Copyright 2011 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 + * 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. + * 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.blob.models; @@ -24,22 +24,22 @@ public enum AccessConditionHeaderType { NONE, /** - * Specifies the <i>If-Unmodified-Since</i> conditional header is set. + * Specifies the <code>If-Unmodified-Since</code> conditional header is set. */ IF_UNMODIFIED_SINCE, /** - * Specifies the <i>If-Match</i> conditional header is set. + * Specifies the <code>If-Match</code> conditional header is set. */ IF_MATCH, /** - * Specifies the <i>If-Modified-Since</i> conditional header is set. + * Specifies the <code>If-Modified-Since</code> conditional header is set. */ IF_MODIFIED_SINCE, /** - * Specifies the <i>If-None-Match</i> conditional header is set. + * Specifies the <code>If-None-Match</code> conditional header is set. */ IF_NONE_MATCH; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java index 25daaff571ab7..7965e60deedd1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/CreateBlobSnapshotResult.java @@ -46,7 +46,7 @@ public String getSnapshot() { } /** - * Reserved for internal use. Sets the snapshot timestamp value from the <strong>x-ms-snapshot</strong> header + * Reserved for internal use. Sets the snapshot timestamp value from the <code>x-ms-snapshot</code> header * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -75,7 +75,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the ETag of the snapshot from the <strong>ETag</strong> header returned in the + * Reserved for internal use. Sets the ETag of the snapshot from the <code>ETag</code> header returned in the * response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -104,8 +104,8 @@ public Date getLastModified() { } /** - * Reserved for internal use. Sets the last modified time of the snapshot from the <strong>Last-Modified</strong> - * header returned in the response. + * Reserved for internal use. Sets the last modified time of the snapshot from the <code>Last-Modified</code> header + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java index bba525e3db1e3..14aa9fb13e624 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobMetadataResult.java @@ -46,7 +46,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * Reserved for internal use. Sets the ETag of the blob from the <code>ETag</code> header returned in the * response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -75,8 +75,8 @@ public Date getLastModified() { } /** - * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> - * header returned in the response. + * Reserved for internal use. Sets the last modified time of the blob from the <code>Last-Modified</code> header + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. @@ -101,8 +101,8 @@ public HashMap<String, String> getMetadata() { } /** - * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the - * response. + * Reserved for internal use. Sets the blob metadata from the <code>x-ms-meta-<em>name:value</em></code> headers + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java index db77763781ff5..725ce6d3c78ae 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobPropertiesResult.java @@ -66,8 +66,8 @@ public HashMap<String, String> getMetadata() { } /** - * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the - * response. + * Reserved for internal use. Sets the blob metadata from the <code>x-ms-meta-<em>name:value</em></code> headers + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java index 98a86f38291e7..dcdc580a79eab 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetBlobResult.java @@ -90,8 +90,8 @@ public HashMap<String, String> getMetadata() { } /** - * Reserved for internal use. Sets the blob metadata from the <em>x-ms-meta-name:value</em> headers returned in the - * response. + * Reserved for internal use. Sets the blob metadata from the <code>x-ms-meta-<em>name:value</em></code> headers + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java index 00ad2de3de558..6a5cf9495ab06 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/GetContainerPropertiesResult.java @@ -48,7 +48,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the Etag of the container from the <strong>ETag</strong> header returned in + * Reserved for internal use. Sets the Etag of the container from the <code>ETag</code> header returned in * the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -74,8 +74,8 @@ public Date getLastModified() { } /** - * Reserved for internal use. Sets the last modified time of the container from the - * <strong>Last-Modified</strong> header returned in the response. + * Reserved for internal use. Sets the last modified time of the container from the <code>Last-Modified</code> + * header returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. @@ -100,7 +100,7 @@ public HashMap<String, String> getMetadata() { } /** - * Reserved for internal use. Sets the container metadata from the <strong>x-ms-meta-<em>name</em></strong> headers + * Reserved for internal use. Sets the container metadata from the <code>x-ms-meta-<em>name</em></code> headers * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java index 43008a72f44d6..216322e4dd212 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobBlocksResult.java @@ -85,7 +85,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * Reserved for internal use. Sets the ETag of the blob from the <code>ETag</code> header returned in the * response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -109,8 +109,8 @@ public String getContentType() { } /** - * Reserved for internal use. Sets the MIME content type of the blob from the <strong>Content-Type</strong> - * header returned in the response. + * Reserved for internal use. Sets the MIME content type of the blob from the <code>Content-Type</code> header + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. @@ -133,7 +133,7 @@ public long getContentLength() { } /** - * Reserved for internal use. Sets the content length of the blob from the <strong>x-ms-blob-content-length</strong> + * Reserved for internal use. Sets the content length of the blob from the <code>x-ms-blob-content-length</code> * header returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java index ffec19183345f..c18ccb70c38b9 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobRegionsResult.java @@ -51,8 +51,8 @@ public Date getLastModified() { } /** - * Reserved for internal use. Sets the last modified time of the blob from the <strong>Last-Modified</strong> - * header returned in the response. + * Reserved for internal use. Sets the last modified time of the blob from the <code>Last-Modified</code> header + * returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by * the server. @@ -78,7 +78,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the ETag of the blob from the <strong>ETag</strong> header returned in the + * Reserved for internal use. Sets the ETag of the blob from the <code>ETag</code> header returned in the * response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by @@ -102,7 +102,7 @@ public long getContentLength() { } /** - * Reserved for internal use. Sets the content length of the blob from the <strong>x-ms-blob-content-length</strong> + * Reserved for internal use. Sets the content length of the blob from the <code>x-ms-blob-content-length</code> * header returned in the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned by diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java index c16f54bdb6c10..ed46f9f717727 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsOptions.java @@ -190,7 +190,7 @@ public String getDelimiter() { * each blob does. * <p> * Note that if a delimiter is set, you cannot include snapshots. A request that includes both returns an - * InvalidQueryParameter error (HTTP status code 400 – Bad Request), which causes a {@link ServiceException} to be + * InvalidQueryParameter error (HTTP status code 400 - Bad Request), which causes a {@link ServiceException} to be * thrown. * <p> * The <em>delimiter</em> value only affects calls made on methods where this {@link ListBlobsOptions} instance is @@ -246,7 +246,7 @@ public boolean isIncludeSnapshots() { * Sets the value of an optional flag indicating whether to include blob snapshots with a response. * <p> * Note that if this flag is set, you cannot set a delimiter. A request that includes both returns an - * InvalidQueryParameter error (HTTP status code 400 – Bad Request), which causes a {@link ServiceException} to be + * InvalidQueryParameter error (HTTP status code 400 - Bad Request), which causes a {@link ServiceException} to be * thrown. * * @param includeSnapshots diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java index feb0bf40944e3..5245eea924277 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListBlobsResult.java @@ -30,11 +30,11 @@ import com.microsoft.windowsazure.services.blob.implementation.MetadataAdapter; /** - * A wrapper class for the response returned from a Blob Service REST API Get Blobs operation. This is returned by + * A wrapper class for the response returned from a Blob Service REST API List Blobs operation. This is returned by * calls to implementations of {@link BlobContract#listBlobs(String)} and * {@link BlobContract#listBlobs(String, ListBlobsOptions)}. * <p> - * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135734.aspx">Get Blobs</a> documentation on + * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135734.aspx">List Blobs</a> documentation on * MSDN for details of the underlying Blob Service REST API operation. */ @XmlRootElement(name = "EnumerationResults") diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java index 2d9bdcca3e1d7..71a5a868c5ca8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersOptions.java @@ -93,7 +93,7 @@ public String getMarker() { * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method on a {@link ListContainersResult} * instance to get the marker value to set on a {@link ListContainersOptions} instance using a call to this method. * Pass the {@link ListContainersOptions} instance as a parameter to a - * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * {@link BlobContract#listContainers(ListContainersOptions)} call to get the next portion of the container list. * <p> * The <em>marker</em> value only affects calls made on methods where this {@link ListContainersOptions} instance is * passed as a parameter. @@ -130,7 +130,7 @@ public int getMaxResults() { * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method on a {@link ListContainersResult} * instance to get the marker value to set on a {@link ListContainersOptions} instance using a call to * {@link ListContainersOptions#setMarker(String)}. Pass the {@link ListContainersOptions} instance as a parameter - * to a {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container + * to a {@link BlobContract#listContainers(ListContainersOptions)} call to get the next portion of the container * list. * <p> * The <em>maxResults</em> value only affects calls made on methods where this {@link ListContainersOptions} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java index eb47ce77be2b2..3b6508eb4a8ae 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/ListContainersResult.java @@ -31,7 +31,7 @@ /** * A wrapper class for the response returned from a Blob Service REST API List Containers operation. This is returned by * calls to implementations of {@link BlobContract#listContainers} and - * {@link BlobContract#listContainers(listContainersOptions)}. + * {@link BlobContract#listContainers(ListContainersOptions)}. * <p> * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179352.aspx">List Containers</a> * documentation on MSDN for details of the underlying Blob Service REST API operation. @@ -135,7 +135,7 @@ public void setPrefix(String prefix) { * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method to get the marker value to set on a * {@link ListContainersOptions} instance using a call to {@link ListContainersOptions#setMarker(String)}. Pass the * {@link ListContainersOptions} instance as a parameter to a - * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * {@link BlobContract#listContainers(ListContainersOptions)} call to get the next portion of the container list. * * @return * A {@link String} containing the marker used to specify the beginning of the container list returned, if @@ -207,7 +207,7 @@ public void setNextMarker(String nextMarker) { * Use the {@link ListContainersResult#getNextMarker() getNextMarker} method to get the marker value to set on a * {@link ListContainersOptions} instance using a call to {@link ListContainersOptions#setMarker(String)}. Pass the * {@link ListContainersOptions} instance as a parameter to a - * {@link BlobContract#listContainers(listContainersOptions)} call to get the next portion of the container list. + * {@link BlobContract#listContainers(ListContainersOptions)} call to get the next portion of the container list. * * @return * The maximum number of container list items to return that was specified in the request, if any. @@ -292,7 +292,7 @@ public void setUrl(String url) { } /** - * Gets the container properties. The container properties include the last modified time and an Etag value. + * Gets the container properties. The container properties include the last modified time and an ETag value. * * @return * A {@link ContainerProperties} instance containing the properties associated with the container. @@ -384,12 +384,12 @@ public void setLastModified(Date lastModified) { } /** - * Gets the Etag of the container. This value can be used when updating or deleting a container using an + * Gets the ETag of the container. This value can be used when updating or deleting a container using an * optimistic concurrency model to prevent the client from modifying data that has been changed by another * client. * * @return - * A {@link String} containing the server-assigned Etag value for the container. + * A {@link String} containing the server-assigned ETag value for the container. */ @XmlElement(name = "Etag") public String getEtag() { @@ -397,14 +397,14 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the Etag of the container from the <strong>Etag</strong> element returned in + * Reserved for internal use. Sets the ETag of the container from the <strong>ETag</strong> element returned in * the response. * <p> * This method is invoked by the API to set the value from the Blob Service REST API operation response returned * by the server. * * @param etag - * A {@link String} containing the server-assigned Etag value for the container. + * A {@link String} containing the server-assigned ETag value for the container. */ public void setEtag(String etag) { this.etag = etag; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java index 1af40b003e052..44a6fe568ca75 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/PageRange.java @@ -23,7 +23,7 @@ * be up to the value of the blob's full size. * <p> * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of 512 - * and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are 0-511, 512-1023, etc. */ public class PageRange { private long start; @@ -39,7 +39,7 @@ public PageRange() { * Creates a page range from the specified start and end byte offsets, inclusive. * <p> * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of - * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are 0-511, 512-1023, etc. * * @param start * The beginning offset value in bytes for the page range, inclusive. @@ -66,7 +66,7 @@ public long getStart() { * Sets the byte offset of the start of the page range within the blob, inclusive. * <p> * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of - * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are 0-511, 512-1023, etc. * * @param start * The beginning offset value in bytes for the page range, inclusive. @@ -93,7 +93,7 @@ public long getEnd() { * Sets the byte offset of the end of the page range within the blob, inclusive. * <p> * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of - * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are 0-511, 512-1023, etc. * * @param end * The ending offset value in bytes for the page range, inclusive. @@ -121,7 +121,7 @@ public long getLength() { * 512 bytes. * <p> * Pages are aligned with 512-byte boundaries. When specifying a page range, the start offset must be a modulus of - * 512 and the end offset must be a modulus of 512 – 1. Examples of valid byte ranges are 0-511, 512-1023, etc. + * 512 and the end offset must be a modulus of 512 - 1. Examples of valid byte ranges are 0-511, 512-1023, etc. * * @param value * The ending offset value in bytes for the page range, inclusive. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/package.html new file mode 100644 index 0000000000000..161010acd8489 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/models/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the blob data transfer object classes. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/package.html new file mode 100644 index 0000000000000..72c5aac9b9f36 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the integrated queue service client classes and implementation. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/package.html new file mode 100644 index 0000000000000..7cfd5ada0c07a --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the implementation of the queue service classes and utilities. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java index 4a6fd5fb37fbc..48b0859ba92f5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java @@ -14,8 +14,39 @@ */ package com.microsoft.windowsazure.services.table; +import com.microsoft.windowsazure.services.table.models.EdmType; + +/** + * An interface for EDM type serialization to Windows Azure Storage service and deserialization to native Java + * types. + */ public interface EdmValueConverter { + /** + * Creates a serialized string in EDM type format from the native Java <em>value</em> parameter. + * <p> + * Supported <em>edmType</em> parameter values are defined as constants in the {@link EdmType} class. + * + * @param edmType + * A {@link String} containing the EDM data type to serialize the <em>value</em> parameter as. + * @param value + * An {@link Object} reference to the native Java value to serialize. + * @return + * A {@link String} containing the serialized data to send to the Windows Azure Storage service. + */ String serialize(String edmType, Object value); + /** + * Creates an object of the correct native Java type from the serialized data in EDM type format. + * <p> + * Supported <em>edmType</em> parameter values are defined as constants in the {@link EdmType} class. + * + * @param edmType + * A {@link String} containing the EDM data type of the <em>value</em> parameter to deserialize. + * @param value + * A {@link String} containing the Windows Azure Storage service data to deserialize. + * @return + * An {@link Object} reference to the deserialized native Java value. This is an object of the + * correct native Java type for the EDM data type, passed as an {@link Object}. + */ Object deserialize(String edmType, String value); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java index 33391fc9e711f..5d0d696061d10 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java @@ -14,8 +14,29 @@ */ package com.microsoft.windowsazure.services.table; +/** + * This class contains static strings used to identify parts of a service configuration instance associated with the + * Windows Azure Table service. + * <p> + * These values must not be altered. + */ public class TableConfiguration { + /** + * The Table configuration account name constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the value for the DNS prefix name for the storage account. + */ public final static String ACCOUNT_NAME = "table.accountName"; + + /** + * The Table configuration account key constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the value for the storage service account key. + */ public final static String ACCOUNT_KEY = "table.accountKey"; + + /** + * The Table configuration URI constant. This <code>String</code> value is used as a key in the + * configuration file, to identify the URI value for the Table service REST API address for the + * storage account. + */ public final static String URI = "table.uri"; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java index c37f20da147f8..51bf07c43bd3f 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java @@ -16,22 +16,69 @@ import com.microsoft.windowsazure.services.core.Configuration; +/** + * A class for static factory methods that return instances implementing {@link TableContract}. + */ public class TableService { private TableService() { } + /** + * A static factory method that returns an instance implementing {@link TableContract} using default values for + * initializing a {@link Configuration} instance. Note that the returned interface will not work unless storage + * account credentials have been added to the "META-INF/com.microsoft.windowsazure.properties" resource file. + * + * @return + * An instance implementing {@link TableContract} for interacting with the Table service. + */ public static TableContract create() { return create(null, Configuration.getInstance()); } + /** + * A static factory method that returns an instance implementing {@link TableContract} using the specified + * {@link Configuration} instance. The {@link Configuration} instance must have storage account information and + * credentials set before this method is called for the returned interface to work. + * + * @param config + * A {@link Configuration} instance configured with storage account information and credentials. + * + * @return + * An instance implementing {@link TableContract} for interacting with the Table service. + */ public static TableContract create(Configuration config) { return create(null, config); } + /** + * A static factory method that returns an instance implementing {@link TableContract} using default values for + * initializing a {@link Configuration} instance, and using the specified profile prefix for service settings. Note + * that the returned interface will not work unless storage account settings and credentials have been added to the + * "META-INF/com.microsoft.windowsazure.properties" resource file with the specified profile prefix. + * + * @param profile + * A string prefix for the account name and credentials settings in the {@link Configuration} instance. + * @return + * An instance implementing {@link TableContract} for interacting with the Table service. + */ public static TableContract create(String profile) { return create(profile, Configuration.getInstance()); } + /** + * A static factory method that returns an instance implementing {@link TableContract} using the specified + * {@link Configuration} instance and profile prefix for service settings. The {@link Configuration} instance must + * have storage account information and credentials set with the specified profile prefix before this method is + * called for the returned interface to work. + * + * @param profile + * A string prefix for the account name and credentials settings in the {@link Configuration} instance. + * @param config + * A {@link Configuration} instance configured with storage account information and credentials. + * + * @return + * An instance implementing {@link TableContract} for interacting with the Table service. + */ public static TableContract create(String profile, Configuration config) { return config.create(profile, TableContract.class); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/package.html new file mode 100644 index 0000000000000..6a84c85156b23 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the integrated table service client classes and implementation. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/package.html new file mode 100644 index 0000000000000..4032bfee5276f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the implementation of the table service classes and utilities. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java index f5e8b072f4c9f..f59811f33f6f1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -41,16 +41,18 @@ * <ul> * <li>The Table service supports only a single change set within a batch. The change set can include multiple insert, * update, and delete operations. If a batch includes more than one change set, the first change set will be processed - * by the service, and additional change sets will be rejected with status code 400 (Bad Request). - * <p> - * <strong>Important</strong> Multiple operations against a single entity are not permitted within a change set.</li> + * by the service, and additional change sets will be rejected with status code 400 (Bad Request). <br> + * <br> + * <strong>Important:</strong> Multiple operations against a single entity are not permitted within a change set.<br> + * <br> + * </li> * <li>Note that a query operation is not permitted within a batch that contains insert, update, or delete operations; * it must be submitted singly in the batch.</li> * <li>Operations within a change set are processed atomically; that is, all operations in the change set either succeed * or fail. Operations are processed in the order they are specified in the change set.</li> * <li>The Table service does not support linking operations in a change set.</li> * <li>The Table service supports a maximum of 100 operations in a change set.</li> - * <ul> + * </ul> * <p> * An individual request within the change set is identical to a request made when that operation is being called by * itself. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java index 1d0a0b006d03b..47496057fb25e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -114,7 +114,7 @@ public String getEtag() { /** * Reserved for internal use. Sets the ETag for the entity updated by the operation from the matching - * <strong>ETag</strong> header within the MIME change set response corresponding to the update request in the + * <code>ETag</code> header within the MIME change set response corresponding to the update request in the * batch transaction. * * @param etag @@ -145,7 +145,7 @@ public static class Error extends Entry { * Gets the {@link ServiceException} instance corresponding to the error returned in the server response. * * @return - * A @link ServiceException} instance corresponding to the error returned in the server response. + * A {@link ServiceException} instance corresponding to the error returned in the server response. */ public ServiceException getError() { return error; @@ -156,7 +156,7 @@ public ServiceException getError() { * within the MIME change set response corresponding to the request in the batch transaction. * * @param error - * A @link ServiceException} instance corresponding to the error returned in the server response. + * A {@link ServiceException} instance corresponding to the error returned in the server response. * @return * A reference to this {@link Error} instance. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java index 55b80c75531ed..888901e840417 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -49,7 +49,7 @@ public class ConstantFilter extends Filter { * Use the static factory method in the {@link Filter} class to create a {@link ConstantFilter}, rather than * constructing one directly. * - * @param propertyName + * @param value * An {@link Object} containing the constant value to use as a filter parameter in a table query request. */ public ConstantFilter(Object value) { diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java index 22e3dda149c82..a104fdc549e5e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java @@ -42,14 +42,8 @@ * <td>A Boolean value.</td> * </tr> * <tr> - * <td><strong>Edm.Byte</strong></td> - * <td>{@link EdmType#BYTE}</td> - * <td><code>byte, Byte</code></td> - * <td>An 8-bit integer value.</td> - * </tr> - * <tr> * <td><strong>Edm.DateTime</strong></td> - * <td>{@link EdmType#DATE_TIME}</td> + * <td>{@link EdmType#DATETIME}</td> * <td><code>Date</code></td> * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java index cd8d483f99422..8377a645fa8c5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -64,14 +64,8 @@ * <td>A Boolean value.</td> * </tr> * <tr> - * <td><strong>Edm.Byte</strong></td> - * <td>{@link EdmType#BYTE}</td> - * <td><code>byte, Byte</code></td> - * <td>An 8-bit integer value.</td> - * </tr> - * <tr> * <td><strong>Edm.DateTime</strong></td> - * <td>{@link EdmType#DATE_TIME}</td> + * <td>{@link EdmType#DATETIME}</td> * <td><code>Date</code></td> * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java index 7ec1fd1e13d6d..612c8c40d0270 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -215,7 +215,9 @@ public static ConstantFilter constant(Object value) { /** * A static factory method that creates a property name value to use as an operand in a {@link BinaryFilter} - * expression. Case is significant for the <strong>PartitionKey</strong> and <strong>RowKey</strong> property names. + * expression. When the filter is evaluated, the content of the named property in the entity is used as the operand. + * <p> + * Note that case is significant for the <strong>PartitionKey</strong> and <strong>RowKey</strong> property names. * * @param value * A {@link String} containing the name of a property. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java index 949e90a91330b..19b7492ec5c94 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java @@ -39,14 +39,8 @@ * <td>A Boolean value.</td> * </tr> * <tr> - * <td><strong>Edm.Byte</strong></td> - * <td>{@link EdmType#BYTE}</td> - * <td><code>byte, Byte</code></td> - * <td>An 8-bit integer value.</td> - * </tr> - * <tr> * <td><strong>Edm.DateTime</strong></td> - * <td>{@link EdmType#DATE_TIME}</td> + * <td>{@link EdmType#DATETIME}</td> * <td><code>Date</code></td> * <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight, * January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java index c21978954c657..9594b838e74e6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java @@ -20,12 +20,16 @@ * Use the static factory method in the {@link Filter} class to create a {@link PropertyNameFilter}, rather than * constructing one directly. * <p> - * Use this class to pass a property name as a filter parameter. Case is significant for the - * <strong>PartitionKey</strong> and <strong>RowKey</strong> property names in a filter. A {@link PropertyNameFilter} - * may be combined in a {@link BinaryFilter} with a comparison operator and a {@link ConstantFilter} to limit query - * results to properties with values that match the {@link ConstantFilter} value. The table service does not support - * wildcard queries, but you can perform prefix matching by using comparison operators on the desired prefix created as - * a {@link String} in a {@link ConstantFilter} instance. + * Use this class to pass a property name as a filter parameter. When the filter is evaluated, the content of the named + * property in the entity is used in the filter expression. + * <p> + * Note that case is significant for the <strong>PartitionKey</strong> and <strong>RowKey</strong> property names in a + * filter. + * <p> + * A {@link PropertyNameFilter} may be combined in a {@link BinaryFilter} with a comparison operator and a + * {@link ConstantFilter} to limit query results to properties with values that match the {@link ConstantFilter} value. + * The table service does not support wildcard queries, but you can perform prefix matching by using comparison + * operators on the desired prefix created as a {@link String} in a {@link ConstantFilter} instance. * <p> * See the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd894031.aspx">Querying Tables and Entities</a> * topic in MSDN for more information on creating table query filter strings. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java index 740c0b9cb1db0..fcc04a34d5104 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -236,7 +236,7 @@ public QueryEntitiesOptions setOrderByFields(List<String> orderByFields) { * Reserved for future use. Adds a property name to the list of property names to use to order the results in the * server response. * - * @param orderByFields + * @param orderByField * A {@link String} containing a property name to add to the list to use to order the results in the * server response. * @return diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java index efe1da871358a..6d050f96843d1 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java @@ -84,7 +84,7 @@ public String getNextPartitionKey() { /** * Reserved for internal use. Sets the next partition key continuation token to resume a query entities request with - * from the <strong>x-ms-continuation-NextRowKey</strong> header returned in the response from the server. + * from the <code>x-ms-continuation-NextPartitionKey</code> header returned in the response from the server. * <p> * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five @@ -128,7 +128,7 @@ public String getNextRowKey() { /** * Reserved for internal use. Sets the next row key continuation token to resume a query entities request with from - * the <strong>x-ms-continuation-NextRowKey</strong> header returned in the response from the server. + * the <code>x-ms-continuation-NextRowKey</code> header returned in the response from the server. * <p> * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java index 2b8e98e62baa5..3cfa1768ea814 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -55,7 +55,7 @@ public String getNextTableName() { /** * Reserved for internal use. Sets the next table name continuation token to resume a query tables request with from - * the <strong>x-ms-continuation-NextTableName</strong> header returned in the response from the server. + * the <code>x-ms-continuation-NextTableName</code> header returned in the response from the server. * <p> * A query against the Table service may return a maximum of 1,000 items at one time and may execute for a maximum * of five seconds. If the result set contains more than 1,000 items, if the query did not complete within five @@ -68,8 +68,6 @@ public String getNextTableName() { * @param nextTableName * A {@link String} containing the next table name continuation token to use to resume a query tables * request with. - * @return - * A reference to this {@link QueryTablesOptions} instance. */ public void setNextTableName(String nextTableName) { this.nextTableName = nextTableName; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java index 0e21304ccaa97..53dc66738944e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java @@ -45,7 +45,7 @@ public String getEtag() { } /** - * Reserved for internal use. Sets the ETag value of the modified entity from the <strong>ETag</strong> header value + * Reserved for internal use. Sets the ETag value of the modified entity from the <code>ETag</code> header value * in the server response. * <p> * This method is invoked by the API to set the value from the Table Service REST API operation response returned by diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/package.html new file mode 100644 index 0000000000000..5d3fb8ac686a3 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the table data transfer object classes. +</body> +</html> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/package.html b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/package.html new file mode 100644 index 0000000000000..74b2c715f9884 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/package.html @@ -0,0 +1,5 @@ +<html> +<head /> +<body>This package contains the table service class, interface, and associated configuration and utility classes. +</body> +</html> From 7da0b19a1c31136701332332009abe4754569580 Mon Sep 17 00:00:00 2001 From: Justin Yu <justinyu@microsoft.com> Date: Mon, 11 Jun 2012 21:19:53 -0700 Subject: [PATCH 73/76] IaaS change --- .../blob/client/BlobAccessPolicyResponse.java | 88 ++ .../services/blob/client/BlobAttributes.java | 13 + .../services/blob/client/BlobConstants.java | 90 +- .../blob/client/BlobContainerPermissions.java | 10 +- .../blob/client/BlobContainerProperties.java | 69 + .../client/BlobDeserializationHelper.java | 70 +- .../services/blob/client/BlobProperties.java | 48 + .../services/blob/client/BlobRequest.java | 71 + .../services/blob/client/BlobResponse.java | 15 +- .../services/blob/client/CloudBlob.java | 427 ++++-- .../blob/client/CloudBlobContainer.java | 669 ++++++--- .../services/blob/client/CloudBlockBlob.java | 19 + .../services/blob/client/CloudPageBlob.java | 18 + .../blob/client/ContainerRequest.java | 95 +- .../blob/client/ContainerResponse.java | 5 +- .../services/blob/client/CopyState.java | 122 ++ .../services/blob/client/CopyStatus.java | 86 ++ ....java => SharedAccessBlobPermissions.java} | 8 +- ...olicy.java => SharedAccessBlobPolicy.java} | 38 +- .../client/SharedAccessSignatureHelper.java | 248 ---- .../storage/SharedAccessSignatureHelper.java | 497 +++++++ .../storage/AccessPolicyResponseBase.java} | 91 +- .../services/core/storage/Constants.java | 192 ++- .../services/core/storage/Credentials.java | 47 + .../services/core/storage/LeaseDuration.java | 62 + .../services/core/storage/LeaseState.java | 86 ++ .../services/core/storage/LeaseStatus.java | 12 +- .../StorageCredentialsAccountAndKey.java | 16 + .../core/storage/StorageErrorCode.java | 17 +- .../core/storage/utils/PathUtility.java | 27 +- .../utils/implementation/BaseResponse.java | 99 ++ .../utils/implementation/Canonicalizer.java | 3 +- .../utils/implementation/LeaseAction.java | 9 +- .../services/queue/client/CloudQueue.java | 374 ++++- .../client/QueueAccessPolicyResponse.java | 88 ++ .../queue/client/QueuePermissions.java | 57 + .../services/queue/client/QueueRequest.java | 119 ++ .../client/SharedAccessQueuePermissions.java | 91 ++ .../queue/client/SharedAccessQueuePolicy.java | 174 +++ .../services/table/client/CloudTable.java | 643 +++++++++ .../table/client/CloudTableClient.java | 423 +----- .../table/client/QueryTableOperation.java | 4 +- .../client/SharedAccessTablePermissions.java | 91 ++ .../table/client/SharedAccessTablePolicy.java | 173 +++ .../client/TableAccessPolicyResponse.java | 88 ++ .../table/client/TableBatchOperation.java | 2 +- .../services/table/client/TableOperation.java | 8 +- .../table/client/TablePermissions.java | 55 + .../services/table/client/TableRequest.java | 153 ++ .../services/blob/client/BlobTestBase.java | 73 + .../blob/client/CloudBlobContainerTests.java | 501 +++++++ .../queue/client/CloudQueueClientTests.java | 218 +++ .../queue/client/CloudQueueTests.java | 1273 +++++++++++++++++ .../services/queue/client/QueueTestBase.java | 87 ++ .../table/client/TableClientTests.java | 430 +++++- .../services/table/client/TableTestBase.java | 20 +- 56 files changed, 7209 insertions(+), 1303 deletions(-) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/{SharedAccessPermissions.java => SharedAccessBlobPermissions.java} (87%) rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/{SharedAccessPolicy.java => SharedAccessBlobPolicy.java} (77%) delete mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java rename microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/{blob/client/AccessPolicyResponse.java => core/storage/AccessPolicyResponseBase.java} (64%) create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java create mode 100644 microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java create mode 100644 microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java new file mode 100644 index 0000000000000..66313bc04ebf1 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.blob.client; + +import java.io.InputStream; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class BlobAccessPolicyResponse extends AccessPolicyResponseBase<SharedAccessBlobPolicy> { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public BlobAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessBlobPolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessBlobPolicy retPolicy = new SharedAccessBlobPolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessBlobPolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java index 90d9f6b2d0ab3..f0f782200ab70 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobAttributes.java @@ -28,6 +28,11 @@ final class BlobAttributes { */ private HashMap<String, String> metadata; + /** + * Represents the state of the most recent or pending copy operation. + */ + private CopyState copyState; + /** * Holds the properties of the blob. */ @@ -55,6 +60,10 @@ public HashMap<String, String> getMetadata() { return this.metadata; } + public CopyState getCopyState() { + return this.copyState; + } + public BlobProperties getProperties() { return this.properties; } @@ -66,4 +75,8 @@ protected void setMetadata(final HashMap<String, String> metadata) { protected void setProperties(final BlobProperties properties) { this.properties = properties; } + + public void setCopyState(final CopyState copyState) { + this.copyState = copyState; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java index b1cca6aed131b..8f3acd35234bd 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobConstants.java @@ -18,56 +18,6 @@ * Holds the Constants used for the Queue Service. */ final class BlobConstants { - /** - * Defines constants for use with query strings. - */ - public static class QueryConstants { - /** - * The query component for the SAS signature. - */ - public static final String SIGNATURE = "sig"; - - /** - * The query component for the signed SAS expiry time. - */ - public static final String SIGNED_EXPIRY = "se"; - - /** - * The query component for the signed SAS identifier. - */ - public static final String SIGNED_IDENTIFIER = "si"; - - /** - * The query component for the signed SAS permissions. - */ - public static final String SIGNED_PERMISSIONS = "sp"; - - /** - * The query component for the signed SAS resource. - */ - public static final String SIGNED_RESOURCE = "sr"; - - /** - * The query component for the signed SAS start time. - */ - public static final String SIGNED_START = "st"; - - /** - * The query component for the signed SAS version. - */ - public static final String SIGNED_VERSION = "sv"; - - /** - * The query component for snapshot time. - */ - public static final String SNAPSHOT = "snapshot"; - } - - /** - * XML element for an access policy. - */ - public static final String ACCESS_POLICY = "AccessPolicy"; - /** * XML element for authentication error details. */ @@ -204,11 +154,6 @@ public static class QueryConstants { */ public static final int DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES = 4 * com.microsoft.windowsazure.services.core.storage.Constants.MB; - /** - * XML element for the end time of an access policy. - */ - public static final String EXPIRY = "Expiry"; - /** * Specifies snapshots are to be included. */ @@ -219,11 +164,6 @@ public static class QueryConstants { */ public static final String LATEST_ELEMENT = "Latest"; - /** - * Maximum number of shared access policy identifiers supported by server. - */ - public static final int MAX_SHARED_ACCESS_POLICY_IDENTIFIERS = 5; - /** * The maximum size, in bytes, of a blob before it must be separated into blocks */ @@ -261,11 +201,6 @@ public static class QueryConstants { public static final String PAGE_WRITE = com.microsoft.windowsazure.services.core.storage.Constants.PREFIX_FOR_STORAGE_HEADER + "page-write"; - /** - * XML element for the permission of an access policy. - */ - public static final String PERMISSION = "Permission"; - /** * XML element for properties. */ @@ -277,16 +212,6 @@ public static class QueryConstants { public static final String SEQUENCE_NUMBER = com.microsoft.windowsazure.services.core.storage.Constants.PREFIX_FOR_STORAGE_HEADER + "blob-sequence-number"; - /** - * XML element for a signed identifier. - */ - public static final String SIGNED_IDENTIFIER_ELEMENT = "SignedIdentifier"; - - /** - * XML element for signed identifiers. - */ - public static final String SIGNED_IDENTIFIERS_ELEMENT = "SignedIdentifiers"; - /** * The header for the blob content length. */ @@ -319,11 +244,6 @@ public static class QueryConstants { */ public static final String SNAPSHOTS_ONLY_VALUE = "only"; - /** - * XML element for the start time of an access policy. - */ - public static final String START = "Start"; - /** * XML element for page range start elements. */ @@ -339,6 +259,16 @@ public static class QueryConstants { */ public static final String UNCOMMITTED_ELEMENT = "Uncommitted"; + /** + * The default timeout of a copy operation. + */ + public static final int DEFAULT_COPY_TIMEOUT_IN_SECONDS = 3600; + + /** + * The default polling interval of a copy operation. + */ + public static final int DEFAULT_POLLING_INTERVAL_IN_SECONDS = 30; + /** * Private Default Ctor */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java index 15b2cc0065d4a..c5eabded38126 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerPermissions.java @@ -55,14 +55,14 @@ public final class BlobContainerPermissions { /** * Gets the set of shared access policies for the container. */ - private HashMap<String, SharedAccessPolicy> sharedAccessPolicies; + private HashMap<String, SharedAccessBlobPolicy> sharedAccessPolicies; /** * Creates an instance of the <code>BlobContainerPermissions</code> class. */ public BlobContainerPermissions() { this.setPublicAccess(BlobContainerPublicAccessType.OFF); - this.sharedAccessPolicies = new HashMap<String, SharedAccessPolicy>(); + this.sharedAccessPolicies = new HashMap<String, SharedAccessBlobPolicy>(); } /** @@ -75,10 +75,10 @@ public BlobContainerPublicAccessType getPublicAccess() { /** * Returns the set of shared access policies for the container. * - * @return A <code>HashMap</code> object of {@link SharedAccessPolicy} objects that represent the set of shared + * @return A <code>HashMap</code> object of {@link SharedAccessBlobPolicy} objects that represent the set of shared * access policies for the container. */ - public HashMap<String, SharedAccessPolicy> getSharedAccessPolicies() { + public HashMap<String, SharedAccessBlobPolicy> getSharedAccessPolicies() { return this.sharedAccessPolicies; } @@ -94,7 +94,7 @@ public void setPublicAccess(final BlobContainerPublicAccessType publicAccess) { * @param sharedAccessPolicies * the sharedAccessPolicies to set */ - public void setSharedAccessPolicies(final HashMap<String, SharedAccessPolicy> sharedAccessPolicies) { + public void setSharedAccessPolicies(final HashMap<String, SharedAccessBlobPolicy> sharedAccessPolicies) { this.sharedAccessPolicies = sharedAccessPolicies; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java index 412aa9437158e..4b34574466fd8 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobContainerProperties.java @@ -17,6 +17,9 @@ import java.util.Date; import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; +import com.microsoft.windowsazure.services.core.storage.LeaseStatus; /** * Represents the system properties for a container. @@ -39,6 +42,21 @@ public final class BlobContainerProperties { */ private Date lastModified; + /** + * Represents the container's lease status. + */ + private LeaseStatus leaseStatus; + + /** + * Represents the container's lease state. + */ + private LeaseState leaseState; + + /** + * Represents the container's lease duration. + */ + private LeaseDuration leaseDuration; + /** * @return the etag */ @@ -53,6 +71,27 @@ public Date getLastModified() { return this.lastModified; } + /** + * @return the leaseStatus + */ + public LeaseStatus getLeaseStatus() { + return this.leaseStatus; + } + + /** + * @return the leaseState + */ + public LeaseState getLeaseState() { + return this.leaseState; + } + + /** + * @return the leaseDuration + */ + public LeaseDuration getLeaseDuration() { + return this.leaseDuration; + } + /** * @param etag * the etag to set @@ -68,4 +107,34 @@ public void setEtag(final String etag) { public void setLastModified(final Date lastModified) { this.lastModified = lastModified; } + + /** + * Reserved for internal use. + * + * @param leaseStatus + * the leaseStatus to set + */ + public void setLeaseStatus(final LeaseStatus leaseStatus) { + this.leaseStatus = leaseStatus; + } + + /** + * Reserved for internal use. + * + * @param LeaseState + * the LeaseState to set + */ + public void setLeaseState(final LeaseState leaseState) { + this.leaseState = leaseState; + } + + /** + * Reserved for internal use. + * + * @param LeaseDuration + * the LeaseDuration to set + */ + public void setLeaseDuration(final LeaseDuration leaseDuration) { + this.leaseDuration = leaseDuration; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java index f10adc792dd36..10fda315c1fb0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobDeserializationHelper.java @@ -25,6 +25,8 @@ import javax.xml.stream.XMLStreamReader; import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; import com.microsoft.windowsazure.services.core.storage.LeaseStatus; import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; @@ -61,6 +63,7 @@ protected static CloudBlob readBlob(final XMLStreamReader xmlr, final CloudBlobC String urlString = null; HashMap<String, String> metadata = null; BlobProperties properties = null; + CopyState copyState = null; int eventType = xmlr.getEventType(); // check if there are more events in the input stream @@ -86,6 +89,49 @@ else if (name.equals(Constants.METADATA_ELEMENT)) { metadata = DeserializationHelper.parseMetadateFromXML(xmlr); xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.METADATA_ELEMENT); } + else if (name.equals(Constants.COPY_ID_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setCopyId(Utility.readElementFromXMLReader(xmlr, Constants.COPY_ID_ELEMENT)); + } + else if (name.equals(Constants.COPY_COMPLETION_TIME_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(Utility + .readElementFromXMLReader(xmlr, Constants.COPY_COMPLETION_TIME_ELEMENT))); + } + else if (name.equals(Constants.COPY_STATUS_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setStatus(CopyStatus.parse(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_ELEMENT))); + } + else if (name.equals(Constants.COPY_SOURCE_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setSource(new URI(Utility.readElementFromXMLReader(xmlr, Constants.COPY_SOURCE_ELEMENT))); + } + else if (name.equals(Constants.COPY_PROGRESS_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.COPY_PROGRESS_ELEMENT); + String[] progressSequence = tempString.split("/"); + copyState.setBytesCopied(Long.parseLong(progressSequence[0])); + copyState.setTotalBytes(Long.parseLong(progressSequence[1])); + } + else if (name.equals(Constants.COPY_STATUS_DESCRIPTION_ELEMENT)) { + if (copyState == null) { + copyState = new CopyState(); + } + copyState.setStatusDescription(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_DESCRIPTION_ELEMENT)); + } } else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstants.BLOB_ELEMENT)) { break; @@ -125,6 +171,7 @@ else if (properties.getBlobType() == BlobType.PAGE_BLOB) { retBlob.snapshotID = snapshotID; retBlob.properties = properties; retBlob.metadata = metadata; + retBlob.copyState = copyState; return retBlob; } else { @@ -268,6 +315,18 @@ protected static BlobContainerProperties readBlobContainerProperties(final XMLSt else if (name.equals(Constants.ETAG_ELEMENT)) { properties.setEtag(Utility.readElementFromXMLReader(xmlr, Constants.ETAG_ELEMENT)); } + else if (name.equals(Constants.LEASE_STATUS_ELEMENT)) { + properties.setLeaseStatus(LeaseStatus.parse(Utility.readElementFromXMLReader(xmlr, + Constants.COPY_STATUS_ELEMENT))); + } + else if (name.equals(Constants.LEASE_STATE_ELEMENT)) { + properties.setLeaseState(LeaseState.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_STATE_ELEMENT))); + } + else if (name.equals(Constants.LEASE_DURATION_ELEMENT)) { + properties.setLeaseDuration(LeaseDuration.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_DURATION_ELEMENT))); + } } else { // expect end of properties @@ -343,9 +402,10 @@ else if (name.equals(BlobConstants.BLOB_PREFIX_ELEMENT)) { * @throws ParseException * if a date value is not correctly encoded * @throws StorageException + * @throws URISyntaxException */ protected static BlobProperties readBlobProperties(final XMLStreamReader xmlr) throws XMLStreamException, - ParseException, StorageException { + ParseException, StorageException, URISyntaxException { xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.PROPERTIES); int eventType = xmlr.getEventType(); final BlobProperties properties = new BlobProperties(); @@ -422,6 +482,14 @@ else if (tempString.equals(Constants.UNLOCKED_VALUE.toLowerCase())) { Constants.HeaderConstants.HTTP_UNUSED_306, null, null); } } + else if (name.equals(Constants.LEASE_STATE_ELEMENT)) { + properties.setLeaseState(LeaseState.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_STATE_ELEMENT))); + } + else if (name.equals(Constants.LEASE_DURATION_ELEMENT)) { + properties.setLeaseDuration(LeaseDuration.parse(Utility.readElementFromXMLReader(xmlr, + Constants.LEASE_DURATION_ELEMENT))); + } } else if (eventType == XMLStreamConstants.END_ELEMENT) { // expect end of properties diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java index 7e8f7ddf660e4..81c3faae35c57 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobProperties.java @@ -17,6 +17,8 @@ import java.util.Date; import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; import com.microsoft.windowsazure.services.core.storage.LeaseStatus; /** @@ -78,6 +80,16 @@ public final class BlobProperties { */ private LeaseStatus leaseStatus = com.microsoft.windowsazure.services.core.storage.LeaseStatus.UNLOCKED; + /** + * Represents the blob's lease state. + */ + private LeaseState leaseState; + + /** + * Represents the blob's lease duration. + */ + private LeaseDuration leaseDuration; + /** * Represents the size, in bytes, of the blob. */ @@ -104,6 +116,8 @@ public BlobProperties(final BlobProperties other) { this.contentType = other.contentType; this.etag = other.etag; this.leaseStatus = other.leaseStatus; + this.leaseState = other.leaseState; + this.leaseDuration = other.leaseDuration; this.length = other.length; this.lastModified = other.lastModified; this.contentMD5 = other.contentMD5; @@ -184,6 +198,20 @@ public LeaseStatus getLeaseStatus() { return this.leaseStatus; } + /** + * @return the leaseState + */ + public LeaseState getLeaseState() { + return this.leaseState; + } + + /** + * @return the leaseDuration + */ + public LeaseDuration getLeaseDuration() { + return this.leaseDuration; + } + /** * @return the length */ @@ -271,6 +299,26 @@ public void setLeaseStatus(final LeaseStatus leaseStatus) { this.leaseStatus = leaseStatus; } + /** + * Reserved for internal use. + * + * @param LeaseState + * the LeaseState to set + */ + public void setLeaseState(final LeaseState leaseState) { + this.leaseState = leaseState; + } + + /** + * Reserved for internal use. + * + * @param LeaseDuration + * the LeaseDuration to set + */ + public void setLeaseDuration(final LeaseDuration leaseDuration) { + this.leaseDuration = leaseDuration; + } + /** * Reserved for internal use. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java index a328318fcdb63..52e9cca26ae60 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobRequest.java @@ -122,6 +122,52 @@ public static HttpURLConnection copyFrom(final URI uri, final int timeout, Strin return request; } + /** + * Generates a web request to abort a copy operation. + * + * @param uri + * The absolute URI to the container. + * @param timeout + * The server timeout interval. + * @param copyId + * A <code>String</code> object that identifying the copy operation. + * @param accessCondition + * The access condition to apply to the request. Only lease conditions are supported for this operation. + * @param blobOptions + * the options to use for the request. + * @param opContext + * a tracking object for the request + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + * @throws IOException + * @throws URISyntaxException + */ + public static HttpURLConnection abortCopy(final URI uri, final int timeout, final String copyId, + final AccessCondition accessCondition, final BlobRequestOptions blobOptions, + final OperationContext opContext) throws StorageException, IOException, URISyntaxException { + + final UriQueryBuilder builder = new UriQueryBuilder(); + + builder.add("comp", "copy"); + builder.add("copyid", copyId); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setFixedLengthStreamingMode(0); + request.setDoOutput(true); + request.setRequestMethod("PUT"); + + request.setRequestProperty("x-ms-copy-action", "abort"); + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request, true); + } + + return request; + } + /** * Creates the web request. * @@ -431,6 +477,19 @@ public static HttpURLConnection getProperties(final URI uri, final int timeout, * The server timeout interval * @param action * the LeaseAction to perform + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param blobOptions @@ -447,6 +506,7 @@ public static HttpURLConnection getProperties(final URI uri, final int timeout, * @throws IllegalArgumentException */ public static HttpURLConnection lease(final URI uri, final int timeout, final LeaseAction action, + final Integer leaseTimeInSeconds, final String proposedLeaseId, final Integer breakPeriodInSeconds, final AccessCondition accessCondition, final BlobRequestOptions blobOptions, final OperationContext opContext) throws IOException, URISyntaxException, StorageException { @@ -461,6 +521,17 @@ public static HttpURLConnection lease(final URI uri, final int timeout, final Le request.setFixedLengthStreamingMode(0); request.setRequestProperty("x-ms-lease-action", action.toString()); + if (leaseTimeInSeconds != null) { + request.setRequestProperty("x-ms-lease-duration", leaseTimeInSeconds.toString()); + } + else { + request.setRequestProperty("x-ms-lease-duration", "-1"); + } + + if (proposedLeaseId != null) { + request.setRequestProperty("x-ms-proposed-lease-id", proposedLeaseId); + } + if (accessCondition != null) { accessCondition.applyConditionToRequest(request); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java index 6871da71e97b1..ea4f9ab2f974a 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/BlobResponse.java @@ -16,6 +16,8 @@ import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; import java.util.Calendar; import java.util.Date; @@ -41,9 +43,11 @@ final class BlobResponse extends BaseResponse { * @param opContext * a tracking object for the request * @return the BlobAttributes from the given request + * @throws ParseException + * @throws URISyntaxException */ public static BlobAttributes getAttributes(final HttpURLConnection request, final URI resourceURI, - final String snapshotID, final OperationContext opContext) { + final String snapshotID, final OperationContext opContext) throws URISyntaxException, ParseException { final String blobType = request.getHeaderField(BlobConstants.BLOB_TYPE_HEADER); final BlobAttributes attributes = new BlobAttributes(BlobType.parse(blobType)); @@ -61,10 +65,9 @@ public static BlobAttributes getAttributes(final HttpURLConnection request, fina lastModifiedCalendar.setTime(new Date(request.getLastModified())); properties.setLastModified(lastModifiedCalendar.getTime()); - final String leaseStatus = request.getHeaderField(Constants.HeaderConstants.LEASE_STATUS); - if (!Utility.isNullOrEmpty(leaseStatus)) { - properties.setLeaseStatus(com.microsoft.windowsazure.services.core.storage.LeaseStatus.parse(leaseStatus)); - } + properties.setLeaseStatus(BaseResponse.getLeaseStatus(request)); + properties.setLeaseState(BaseResponse.getLeaseState(request)); + properties.setLeaseDuration(BaseResponse.getLeaseDuration(request)); final String rangeHeader = request.getHeaderField(Constants.HeaderConstants.CONTENT_RANGE); final String xContentLengthHeader = request.getHeaderField(BlobConstants.CONTENT_LENGTH_HEADER); @@ -89,6 +92,8 @@ else if (!Utility.isNullOrEmpty(xContentLengthHeader)) { attributes.snapshotID = snapshotID; attributes.setMetadata(getMetadata(request)); + + attributes.setCopyState(BaseResponse.getCopyState(request)); return attributes; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java index e108358441b49..a434b75f3de7b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlob.java @@ -27,6 +27,7 @@ import java.util.Date; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; @@ -43,6 +44,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.StreamMd5AndLength; import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseResponse; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -73,6 +75,11 @@ public abstract class CloudBlob implements ListBlobItem { */ String snapshotID; + /** + * Represents the state of the most recent or pending copy operation. + */ + CopyState copyState; + /** * Holds the Blobs container Reference. */ @@ -122,7 +129,6 @@ protected CloudBlob(final BlobType type, final URI uri, final CloudBlobClient cl this(type); Utility.assertNotNull("blobAbsoluteUri", uri); - Utility.assertNotNull("serviceClient", client); this.blobServiceClient = client; this.uri = uri; @@ -207,24 +213,42 @@ protected CloudBlob(final CloudBlob otherBlob) { this.parent = otherBlob.parent; this.blobServiceClient = otherBlob.blobServiceClient; this.name = otherBlob.name; + this.copyState = otherBlob.copyState; } /** * Acquires a new lease on the blob. * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * * @return A <code>String</code> that represents the lease ID. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public final String acquireLease() throws StorageException { - return this.acquireLease(null, null, null); + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId) + throws StorageException { + return this.acquireLease(leaseTimeInSeconds, proposedLeaseId, null, null, null); } /** * Acquires a new lease on the blob using the specified request options and operation context. * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param options @@ -242,8 +266,9 @@ public final String acquireLease() throws StorageException { * If a storage service error occurred. */ @DoesServiceRequest - public final String acquireLease(final AccessCondition accessCondition, BlobRequestOptions options, - OperationContext opContext) throws StorageException { + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -263,8 +288,8 @@ public String execute(final CloudBlobClient client, final CloudBlob blob, final final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, accessCondition, - blobOptions, opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, leaseTimeInSeconds, + proposedLeaseId, null, accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -316,20 +341,28 @@ protected final void assertCorrectBlobType() throws StorageException { * Breaks the lease but ensures that another client cannot acquire a new lease until the current lease period has * expired. * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @return The time, in seconds, remaining in the lease period. * * @throws StorageException * If a storage service error occurred. */ @DoesServiceRequest - public final long breakLease() throws StorageException { - return this.breakLease(null, null, null); + public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException { + return this.breakLease(breakPeriodInSeconds, null, null, null); } /** * Breaks the lease, using the specified request options and operation context, but ensures that another client * cannot acquire a new lease until the current lease period has expired. * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * * @param accessCondition * An {@link AccessCondition} object that represents the access conditions for the blob. * @param options @@ -347,8 +380,8 @@ public final long breakLease() throws StorageException { * If a storage service error occurred. */ @DoesServiceRequest - public final long breakLease(final AccessCondition accessCondition, BlobRequestOptions options, - OperationContext opContext) throws StorageException { + public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { opContext = new OperationContext(); } @@ -368,8 +401,8 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, + breakPeriodInSeconds, accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -401,9 +434,10 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException */ @DoesServiceRequest - public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageException { + public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageException, URISyntaxException { this.copyFromBlob(sourceBlob, null, null, null, null); } @@ -428,10 +462,57 @@ public final void copyFromBlob(final CloudBlob sourceBlob) throws StorageExcepti * * @throws StorageException * If a storage service error occurred. + * @throws URISyntaxException * */ @DoesServiceRequest public final void copyFromBlob(final CloudBlob sourceBlob, final AccessCondition sourceAccessCondition, + final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException, URISyntaxException { + this.copyFromBlob(sourceBlob.uri, sourceAccessCondition, destinationAccessCondition, options, opContext); + + } + + /** + * Copies an existing blob's contents, properties, and metadata to this instance of the <code>CloudBlob</code> + * class. + * + * @param source + * A <code>URI</code> The URI of a source blob. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void copyFromBlob(final URI source) throws StorageException { + this.copyFromBlob(source, null, null, null, null); + } + + /** + * Copies an existing blob's contents, properties, and metadata to a new blob, using the specified access + * conditions, lease ID, request options, and operation context. + * + * @param source + * A <code>URI</code> The URI of a source blob. + * @param sourceAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the source blob. + * @param destinationAccessCondition + * An {@link AccessCondition} object that represents the access conditions for the destination blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final void copyFromBlob(final URI source, final AccessCondition sourceAccessCondition, final AccessCondition destinationAccessCondition, BlobRequestOptions options, OperationContext opContext) throws StorageException { if (opContext == null) { @@ -453,15 +534,94 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.copyFrom(blob.getTransformedAddress(opContext), - blobOptions.getTimeoutIntervalInMs(), sourceBlob.getCanonicalName(false), blob.snapshotID, + blobOptions.getTimeoutIntervalInMs(), source.toString(), blob.snapshotID, sourceAccessCondition, destinationAccessCondition, blobOptions, opContext); - BlobRequest.addMetadata(request, sourceBlob.metadata, opContext); + BlobRequest.addMetadata(request, blob.metadata, opContext); client.getCredentials().signRequest(request, 0); this.setResult(ExecutionEngine.processRequest(request, opContext)); - if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updatePropertiesFromResponse(request); + blob.copyState = BaseResponse.getCopyState(request); + + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Aborts an ongoing blob copy operation. + * + * @param copyId + * A <code>String</code> object that identifying the copy operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void abortCopy(final String copyId) throws StorageException { + this.abortCopy(copyId, null, null, null); + } + + /** + * Aborts an ongoing blob copy operation. + * + * @param copyId + * A <code>String</code> object that identifying the copy operation. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + * + */ + @DoesServiceRequest + public final void abortCopy(final String copyId, final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlob, Void> impl = new StorageOperation<CloudBlobClient, CloudBlob, Void>( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlob blob, final OperationContext opContext) + throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = BlobRequest.abortCopy(blob.getTransformedAddress(opContext), + blobOptions.getTimeoutIntervalInMs(), copyId, accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { this.setNonExceptionedRetryableFailure(true); return null; } @@ -996,6 +1156,7 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op blob.properties = retrievedAttributes.getProperties(); blob.metadata = retrievedAttributes.getMetadata(); + blob.copyState = retrievedAttributes.getCopyState(); return null; } @@ -1279,107 +1440,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { opContext); } - /** - * Returns a shared access signature for the blob using the specified shared access policy. Note this does not - * contain the leading "?". - * - * @param policy - * A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access - * signature. - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final SharedAccessPolicy policy) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(policy, null); - } - - /** - * Returns a shared access signature for the blob using the specified shared access policy and operation context. - * Note this does not contain the leading "?". - * - * @param policy - * A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access - * signature. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final SharedAccessPolicy policy, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(policy, null, opContext); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier. Note this does not - * contain the leading "?". - * - * @param groupPolicyIdentifier - * A <code>String</code> that represents the container-level access policy. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final String groupPolicyIdentifier) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(groupPolicyIdentifier, null); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier and operation context. - * Note this does not contain the leading "?". - * - * @param groupPolicyIdentifier - * A <code>String</code> that represents the container-level access policy. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are unable to sign the request or if the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public final String generateSharedAccessSignature(final String groupPolicyIdentifier, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(null, groupPolicyIdentifier, opContext); - } - /** * Returns a shared access signature for the blob using the specified group policy identifier and operation context. * Note this does not contain the leading "?". @@ -1403,12 +1463,8 @@ public final String generateSharedAccessSignature(final String groupPolicyIdenti * @throws StorageException * If a storage service error occurred. */ - private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, OperationContext opContext) throws InvalidKeyException, - StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } + public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { if (!this.blobServiceClient.getCredentials().canCredentialsSignRequest()) { throw new IllegalArgumentException( @@ -1423,7 +1479,7 @@ private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy final String resourceName = this.getCanonicalName(true); final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, - groupPolicyIdentifier, resourceName, this.blobServiceClient, opContext); + groupPolicyIdentifier, resourceName, this.blobServiceClient, null); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, groupPolicyIdentifier, "b", signature); @@ -1534,6 +1590,15 @@ public final BlobProperties getProperties() { return this.properties; } + /** + * Returns the blob's copy state. + * + * @return A {@link CopyState} object that represents the copy state of the blob. + */ + public CopyState getCopyState() { + return this.copyState; + } + /** * Returns the snapshot or shared access signature qualified URI for this blob. * @@ -1697,7 +1762,7 @@ public final BlobInputStream openInputStream(final AccessCondition accessConditi * @throws StorageException * If a storage service error occurred. * */ - private void parseURIQueryStringAndVerify(final URI completeUri, final CloudBlobClient existingClient, + protected void parseURIQueryStringAndVerify(final URI completeUri, final CloudBlobClient existingClient, final boolean usePathStyleUris) throws StorageException { Utility.assertNotNull("resourceUri", completeUri); @@ -1833,8 +1898,8 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RELEASE, accessCondition, - blobOptions, opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RELEASE, null, null, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -1913,8 +1978,96 @@ public Void execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RENEW, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.RENEW, null, null, null, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + blob.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Changes an existing lease. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition) + throws StorageException { + this.changeLease(proposedLeaseId, accessCondition, null, null); + } + + /** + * Changes an existing lease using the specified proposedLeaseId, request options and operation context. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlob, Void> impl = new StorageOperation<CloudBlobClient, CloudBlob, Void>( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlob blob, final OperationContext opContext) + throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.CHANGE, null, proposedLeaseId, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); @@ -1964,6 +2117,16 @@ protected final void setProperties(final BlobProperties properties) { this.properties = properties; } + /** + * Reserved for internal use. + * + * @param copyState + * the copyState to set + */ + public void setCopyState(final CopyState copyState) { + this.copyState = copyState; + } + /** * Sets the blob snapshot ID. * @@ -2030,8 +2193,8 @@ public Long execute(final CloudBlobClient client, final CloudBlob blob, final Op final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); final HttpURLConnection request = BlobRequest.lease(blob.getTransformedAddress(opContext), this - .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, accessCondition, blobOptions, - opContext); + .getRequestOptions().getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, null, + accessCondition, blobOptions, opContext); client.getCredentials().signRequest(request, 0L); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java index dffa565ca014b..ab8bd64df564b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainer.java @@ -28,6 +28,8 @@ import javax.xml.stream.XMLStreamException; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; +import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -43,6 +45,7 @@ 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.LazySegmentedIterable; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; @@ -617,7 +620,7 @@ public BlobContainerPermissions execute(final CloudBlobClient client, final Clou final String aclString = ContainerResponse.getAcl(request); final BlobContainerPermissions containerAcl = getContainerAcl(aclString); - final AccessPolicyResponse response = new AccessPolicyResponse(request.getInputStream()); + final BlobAccessPolicyResponse response = new BlobAccessPolicyResponse(request.getInputStream()); for (final String key : response.getAccessIdentifiers().keySet()) { containerAcl.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); @@ -707,232 +710,6 @@ else if (this.getResult().getStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { opContext); } - /** - * Returns a shared access signature for the blob using the specified shared access policy. Note this does not - * contain the leading "?". - * <p> - * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - * <p> - * The parameters of the shared access signature that govern access are: - * <ul> - * <li>The start time at which the signature becomes valid.</li> - * <li>The time at which it expires.</li> - * <li>The permissions that it grants.</li> - * </ul> - * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - * <ul> - * <li> - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.</li> - * <li> - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the <code>groupPolicyIdentifier</code> parameter.</li> - * </ul> - * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224645&clcid=0x409'>Creating a Shared Access Signature</a>. For - * details on container-level access policies, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224646&clcid=0x409'>Specifying a Container-Level Access Policy</a>. - * - * @param policy - * A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access - * signature. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final SharedAccessPolicy policy) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(policy, null); - } - - /** - * Returns a shared access signature for the blob using the shared access policy and operation context. Note this - * does not contain the leading "?". - * <p> - * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - * <p> - * The parameters of the shared access signature that govern access are: - * <ul> - * <li>The start time at which the signature becomes valid.</li> - * <li>The time at which it expires.</li> - * <li>The permissions that it grants.</li> - * </ul> - * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - * <ul> - * <li> - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.</li> - * <li> - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the <code>groupPolicyIdentifier</code> parameter.</li> - * </ul> - * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224645&clcid=0x409'>Creating a Shared Access Signature</a>. For - * details on container-level access policies, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224646&clcid=0x409'>Specifying a Container-Level Access Policy</a>. - * - * @param policy - * A <code>SharedAccessPolicy</code> object that represents the access policy for the shared access - * signature. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final SharedAccessPolicy policy, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(policy, null, opContext); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier. Note this does not - * contain the leading "?". - * <p> - * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - * <p> - * The parameters of the shared access signature that govern access are: - * <ul> - * <li>The start time at which the signature becomes valid.</li> - * <li>The time at which it expires.</li> - * <li>The permissions that it grants.</li> - * </ul> - * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - * <ul> - * <li> - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.</li> - * <li> - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the <code>groupPolicyIdentifier</code> parameter.</li> - * </ul> - * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224645&clcid=0x409'>Creating a Shared Access Signature</a>. For - * details on container-level access policies, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224646&clcid=0x409'>Specifying a Container-Level Access Policy</a>. - * - * @param groupPolicyIdentifier - * A <code>String</code> that represents the container-level access policy. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final String groupPolicyIdentifier) throws InvalidKeyException, - StorageException { - return this.generateSharedAccessSignature(groupPolicyIdentifier, null); - } - - /** - * Returns a shared access signature for the blob using the specified group policy identifier and operation context. - * Note this does not contain the leading "?". - * <p> - * A shared access signature is a token that provides delegated access to blob resources. You can provide this token - * to clients in order to grant them specific permissions to resources for a controlled period of time. A shared - * access signature created for a blob grants access just to the content and metadata of that blob. A shared access - * signature created for a container grants access to the content and metadata of any blob in the container, and to - * the list of blobs in the container. - * <p> - * The parameters of the shared access signature that govern access are: - * <ul> - * <li>The start time at which the signature becomes valid.</li> - * <li>The time at which it expires.</li> - * <li>The permissions that it grants.</li> - * </ul> - * These parameters are specified in an access policy, represented by the {@link SharedAccessPolicy} class. There - * are two ways to specify an access policy: - * <ul> - * <li> - * You can specify it on a single shared access signature. In this case, the interval over which the signature may - * be valid is limited to one hour.</li> - * <li> - * You can specify it by creating a container-level access policy, which can be associated with one or more shared - * access signatures. This approach has the advantage of making it possible to revoke a shared access signature, if - * it should be compromised. To specify that the access policy should be used by the signature, call an overload - * that includes the <code>groupPolicyIdentifier</code> parameter.</li> - * </ul> - * You can also specify some parameters of the access policy on the signature and some on a container-level access - * policy. However, if you specify a parameter in both places, the parameter specified for the signature overrides - * that provided by the container-level access policy. For more information on shared access signatures, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224645&clcid=0x409'>Creating a Shared Access Signature</a>. For - * details on container-level access policies, see <a - * href='http://go.microsoft.com/fwlink/?LinkID=224646&clcid=0x409'>Specifying a Container-Level Access Policy</a>. - * - * @param groupPolicyIdentifier - * A <code>String</code> that represents the container-level access policy. - * @param opContext - * An {@link OperationContext} object that represents the context for the current operation. This object - * is used to track requests to the storage service, and to provide additional runtime information about - * the operation. - * - * @return A <code>String</code> that represents the shared access signature. - * - * @throws IllegalArgumentException - * If the credentials are invalid or the blob is a snapshot. - * @throws InvalidKeyException - * If the credentials are invalid. - * @throws StorageException - * If a storage service error occurred. - */ - public String generateSharedAccessSignature(final String groupPolicyIdentifier, OperationContext opContext) - throws InvalidKeyException, StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } - - return this.generateSharedAccessSignatureCore(null, groupPolicyIdentifier, opContext); - } - /** * Returns a shared access signature for the container. Note this does not contain the leading "?". * @@ -945,12 +722,8 @@ public String generateSharedAccessSignature(final String groupPolicyIdentifier, * @throws StorageException * @throws IllegalArgumentException */ - private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, OperationContext opContext) throws InvalidKeyException, - StorageException { - if (opContext == null) { - opContext = new OperationContext(); - } + public String generateSharedAccessSignature(final SharedAccessBlobPolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { if (!this.blobServiceClient.getCredentials().canCredentialsSignRequest()) { final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the BlobServiceClient."; @@ -960,7 +733,7 @@ private String generateSharedAccessSignatureCore(final SharedAccessPolicy policy final String resourceName = this.getSharedAccessCanonicalName(); final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, - groupPolicyIdentifier, resourceName, this.blobServiceClient, opContext); + groupPolicyIdentifier, resourceName, this.blobServiceClient, null); final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, groupPolicyIdentifier, "c", signature); @@ -1854,4 +1627,432 @@ public Void execute(final CloudBlobClient client, final CloudBlobContainer conta ExecutionEngine .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); } + + /** + * Acquires a new lease on the container. + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @return A <code>String</code> that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId) + throws StorageException { + return this.acquireLease(leaseTimeInSeconds, proposedLeaseId, null, null, null); + } + + /** + * Acquires a new lease on the container using the specified visibilityTimeoutInSeconds, proposedLeaseId, request + * options and operation context. + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A <code>String</code> that represents the lease ID. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final String acquireLease(final Integer leaseTimeInSeconds, final String proposedLeaseId, + final AccessCondition accessCondition, BlobRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlobContainer, String> impl = new StorageOperation<CloudBlobClient, CloudBlobContainer, String>( + options) { + @Override + public String execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.ACQUIRE, leaseTimeInSeconds, proposedLeaseId, null, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_CREATED) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + + return BlobResponse.getLeaseID(request, opContext); + } + }; + + return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Renews an existing lease. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the container. The LeaseID + * is required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void renewLease(final AccessCondition accessCondition) throws StorageException { + this.renewLease(accessCondition, null, null); + } + + /** + * Renews an existing lease using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void renewLease(final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlobContainer, Void> impl = new StorageOperation<CloudBlobClient, CloudBlobContainer, Void>( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.RENEW, null, null, null, accessCondition, blobOptions, + opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Releases the lease on the container. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void releaseLease(final AccessCondition accessCondition) throws StorageException { + this.releaseLease(accessCondition, null, null); + } + + /** + * Releases the lease on the container using the specified request options and operation context. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob.The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void releaseLease(final AccessCondition accessCondition, BlobRequestOptions options, + OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlobContainer, Void> impl = new StorageOperation<CloudBlobClient, CloudBlobContainer, Void>( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.RELEASE, null, null, null, accessCondition, blobOptions, + opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + + /** + * Breaks the lease but ensures that another client cannot acquire a new lease until the current lease period has + * expired. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @return The time, in seconds, remaining in the lease period. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final long breakLease(final Integer breakPeriodInSeconds) throws StorageException { + return this.breakLease(breakPeriodInSeconds, null, null, null); + } + + /** + * Breaks the lease, using the specified request options and operation context, but ensures that another client + * cannot acquire a new lease until the current lease period has expired. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return The time, in seconds, remaining in the lease period. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final long breakLease(final Integer breakPeriodInSeconds, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlobContainer, Long> impl = new StorageOperation<CloudBlobClient, CloudBlobContainer, Long>( + options) { + @Override + public Long execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.BREAK, null, null, breakPeriodInSeconds, + accessCondition, blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_ACCEPTED) { + this.setNonExceptionedRetryableFailure(true); + return -1L; + } + + container.updatePropertiesFromResponse(request); + final String leaseTime = BlobResponse.getLeaseTime(request, opContext); + return Utility.isNullOrEmpty(leaseTime) ? -1L : Long.parseLong(leaseTime); + } + }; + + return ExecutionEngine.executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Changes an existing lease. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition) + throws StorageException { + this.changeLease(proposedLeaseId, accessCondition, null, null); + } + + /** + * Changes an existing lease using the specified proposedLeaseId, request options and operation context. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. The LeaseID is + * required to be set on the AccessCondition. + * @param options + * A {@link BlobRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudBlobClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public final void changeLease(final String proposedLeaseId, final AccessCondition accessCondition, + BlobRequestOptions options, OperationContext opContext) throws StorageException { + Utility.assertNotNull("accessCondition", accessCondition); + Utility.assertNotNullOrEmpty("leaseID", accessCondition.getLeaseID()); + + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new BlobRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.blobServiceClient); + + final StorageOperation<CloudBlobClient, CloudBlobContainer, Void> impl = new StorageOperation<CloudBlobClient, CloudBlobContainer, Void>( + options) { + @Override + public Void execute(final CloudBlobClient client, final CloudBlobContainer container, + final OperationContext opContext) throws Exception { + final BlobRequestOptions blobOptions = (BlobRequestOptions) this.getRequestOptions(); + + final HttpURLConnection request = ContainerRequest.lease(container.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), LeaseAction.CHANGE, null, proposedLeaseId, null, accessCondition, + blobOptions, opContext); + + client.getCredentials().signRequest(request, 0L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + return null; + } + + container.updatePropertiesFromResponse(request); + return null; + } + }; + + ExecutionEngine + .executeWithRetry(this.blobServiceClient, this, impl, options.getRetryPolicyFactory(), opContext); + } + } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java index d6cbf59c3dedc..c36d4993bf27c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudBlockBlob.java @@ -38,6 +38,25 @@ */ public final class CloudBlockBlob extends CloudBlob { + /** + * Creates an instance of the <code>CloudBlockBlob</code> class using the specified relative URI and storage service + * client. + * + * @param uri + * A <code>java.net.URI</code> object that represents the relative URI to the blob, beginning with the + * container name. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudBlockBlob(final URI uri) throws StorageException { + super(BlobType.BLOCK_BLOB); + + Utility.assertNotNull("blobAbsoluteUri", uri); + this.uri = uri; + this.parseURIQueryStringAndVerify(uri, null, Utility.determinePathStyleFromUri(uri, null));; + } + /** * Creates an instance of the <code>CloudBlockBlob</code> class by copying values from another cloud block blob. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java index 9bab8cf99b0f1..d55d8564b0b6d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CloudPageBlob.java @@ -37,6 +37,24 @@ * Represents a Windows Azure page blob. */ public final class CloudPageBlob extends CloudBlob { + /** + * Creates an instance of the <code>CloudPageBlob</code> class using the specified relative URI and storage service + * client. + * + * @param uri + * A <code>java.net.URI</code> object that represents the relative URI to the blob, beginning with the + * container name. + * + * @throws StorageException + * If a storage service error occurred. + */ + public CloudPageBlob(final URI uri) throws StorageException { + super(BlobType.PAGE_BLOB); + + Utility.assertNotNull("blobAbsoluteUri", uri); + this.uri = uri; + this.parseURIQueryStringAndVerify(uri, null, Utility.determinePathStyleFromUri(uri, null));; + } /** * Creates an instance of the <code>CloudPageBlob</code> class by copying values from another page blob. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java index 435421772baa4..4c00cef60effa 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerRequest.java @@ -27,6 +27,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; +import com.microsoft.windowsazure.services.core.storage.AccessCondition; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.Credentials; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -34,6 +35,7 @@ import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseRequest; +import com.microsoft.windowsazure.services.core.storage.utils.implementation.LeaseAction; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ListingContext; /** @@ -291,6 +293,75 @@ public static HttpURLConnection setMetadata(final URI uri, final int timeout, fi return BaseRequest.setMetadata(uri, timeout, containerBuilder, opContext); } + /** + * Constructs a HttpURLConnection to Acquire,Release,Break, or Renew a blob lease. Sign with 0 length. + * + * @param uri + * The absolute URI to the blob + * @param timeout + * The server timeout interval + * @param action + * the LeaseAction to perform + * + * @param visibilityTimeoutInSeconds + * Specifies the the span of time for which to acquire the lease, in seconds. + * If null, an infinite lease will be acquired. If not null, this must be greater than zero. + * + * @param proposedLeaseId + * A <code>String</code> that represents the proposed lease ID for the new lease, + * or null if no lease ID is proposed. + * + * @param breakPeriodInSeconds + * Specifies the amount of time to allow the lease to remain, in seconds. + * If null, the break period is the remainder of the current lease, or zero for infinite leases. + * + * @param accessCondition + * An {@link AccessCondition} object that represents the access conditions for the blob. + * @param blobOptions + * the options to use for the request. + * @param opContext + * a tracking object for the request + * @return a HttpURLConnection to use to perform the operation. + * @throws IOException + * if there is an error opening the connection + * @throws URISyntaxException + * if the resource URI is invalid + * @throws StorageException + * an exception representing any error which occurred during the operation. + * @throws IllegalArgumentException + */ + public static HttpURLConnection lease(final URI uri, final int timeout, final LeaseAction action, + final Integer leaseTimeInSeconds, final String proposedLeaseId, final Integer breakPeriodInSeconds, + final AccessCondition accessCondition, final BlobRequestOptions blobOptions, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + + final UriQueryBuilder builder = getContainerUriQueryBuilder(); + builder.add("comp", "lease"); + + final HttpURLConnection request = createURLConnection(uri, timeout, builder, opContext); + + request.setDoOutput(true); + request.setRequestMethod("PUT"); + request.setFixedLengthStreamingMode(0); + request.setRequestProperty("x-ms-lease-action", action.toString()); + + if (leaseTimeInSeconds != null) { + request.setRequestProperty("x-ms-lease-duration", leaseTimeInSeconds.toString()); + } + else { + request.setRequestProperty("x-ms-lease-duration", "-1"); + } + + if (proposedLeaseId != null) { + request.setRequestProperty("x-ms-proposed-lease-id", proposedLeaseId); + } + + if (accessCondition != null) { + accessCondition.applyConditionToRequest(request); + } + return request; + } + /** * Signs the request for Shared Key authentication. * @@ -330,7 +401,7 @@ public static void signRequestForSharedKeyLite(final HttpURLConnection request, * @throws XMLStreamException */ public static void writeSharedAccessIdentifiersToStream( - final HashMap<String, SharedAccessPolicy> sharedAccessPolicies, final StringWriter outWriter) + final HashMap<String, SharedAccessBlobPolicy> sharedAccessPolicies, final StringWriter outWriter) throws XMLStreamException { Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); Utility.assertNotNull("outWriter", outWriter); @@ -338,44 +409,44 @@ public static void writeSharedAccessIdentifiersToStream( final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); - if (sharedAccessPolicies.keySet().size() > BlobConstants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { final String errorMessage = String .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container.", - sharedAccessPolicies.keySet().size(), BlobConstants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); throw new IllegalArgumentException(errorMessage); } // default is UTF8 xmlw.writeStartDocument(); - xmlw.writeStartElement(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); - for (final Entry<String, SharedAccessPolicy> entry : sharedAccessPolicies.entrySet()) { - final SharedAccessPolicy policy = entry.getValue(); - xmlw.writeStartElement(BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + for (final Entry<String, SharedAccessBlobPolicy> entry : sharedAccessPolicies.entrySet()) { + final SharedAccessBlobPolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); // Set the identifier xmlw.writeStartElement(Constants.ID); xmlw.writeCharacters(entry.getKey()); xmlw.writeEndElement(); - xmlw.writeStartElement(BlobConstants.ACCESS_POLICY); + xmlw.writeStartElement(Constants.ACCESS_POLICY); // Set the Start Time - xmlw.writeStartElement(BlobConstants.START); + xmlw.writeStartElement(Constants.START); xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); // end Start xmlw.writeEndElement(); // Set the Expiry Time - xmlw.writeStartElement(BlobConstants.EXPIRY); + xmlw.writeStartElement(Constants.EXPIRY); xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); // end Expiry xmlw.writeEndElement(); // Set the Permissions - xmlw.writeStartElement(BlobConstants.PERMISSION); - xmlw.writeCharacters(SharedAccessPolicy.permissionsToString(policy.getPermissions())); + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessBlobPolicy.permissionsToString(policy.getPermissions())); // end Permission xmlw.writeEndElement(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java index 87e819cb9c039..f03144533da8c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/ContainerResponse.java @@ -68,9 +68,12 @@ public static BlobContainerAttributes getAttributes(final HttpURLConnection requ final BlobContainerProperties containerProperties = containerAttributes.getProperties(); containerProperties.setEtag(BaseResponse.getEtag(request)); containerProperties.setLastModified(new Date(request.getLastModified())); - containerAttributes.setMetadata(getMetadata(request)); + containerProperties.setLeaseStatus(BaseResponse.getLeaseStatus(request)); + containerProperties.setLeaseState(BaseResponse.getLeaseState(request)); + containerProperties.setLeaseDuration(BaseResponse.getLeaseDuration(request)); + return containerAttributes; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java new file mode 100644 index 0000000000000..6c1a9e921afe0 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyState.java @@ -0,0 +1,122 @@ +/** + * Copyright 2011 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.blob.client; + +import java.net.URI; +import java.util.Date; + +/** + * Represents the attributes of a copy operation. + * + */ +public final class CopyState { + /** + * Holds the Name of the Container + */ + private String copyId; + + /** + * Holds the time the copy operation completed, whether completion was due to a successful copy, abortion, or a + * failure. + */ + private Date completionTime; + + /** + * Holds the status of the copy operation. + */ + private CopyStatus status; + + /** + * Holds the source URI of a copy operation. + */ + private URI source; + + /** + * Holds the number of bytes copied in the operation so far. + */ + private Long bytesCopied; + + /** + * Holds the total number of bytes in the source of the copy. + */ + private Long totalBytes; + + /** + * Holds the description of the current status. + */ + private String statusDescription; + + /** + * Initializes a new instance of the CopyState class + */ + public CopyState() { + } + + public String getCopyId() { + return this.copyId; + } + + public Date getCompletionTime() { + return this.completionTime; + } + + public CopyStatus getStatus() { + return this.status; + } + + public URI getSource() { + return this.source; + } + + public Long getBytesCopied() { + return this.bytesCopied; + } + + public Long getTotalBytes() { + return this.totalBytes; + } + + public String getStatusDescription() { + return this.statusDescription; + } + + public void setCopyId(final String copyId) { + this.copyId = copyId; + } + + public void setCompletionTime(final Date completionTime) { + this.completionTime = completionTime; + } + + public void setStatus(final CopyStatus status) { + this.status = status; + } + + public void setSource(final URI source) { + this.source = source; + } + + public void setBytesCopied(final Long bytesCopied) { + this.bytesCopied = bytesCopied; + } + + public void setTotalBytes(final Long totalBytes) { + this.totalBytes = totalBytes; + } + + public void setStatusDescription(final String statusDescription) { + this.statusDescription = statusDescription; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java new file mode 100644 index 0000000000000..9e4d13c91dab5 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/CopyStatus.java @@ -0,0 +1,86 @@ +/** + * Copyright 2011 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.blob.client; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * Represents the status of a copy blob operation. + */ +public enum CopyStatus { + /** + * The copy status is not specified.. + */ + UNSPECIFIED, + + /** + * The copy status is invalid. + */ + INVALID, + + /** + * The copy operation is pending. + */ + PENDING, + + /** + * The copy operation succeeded. + */ + SUCCESS, + + /** + * The copy operation has been aborted. + */ + ABORTED, + + /** + * The copy operation encountered an error. + */ + FAILED; + + /** + * Parses a copy status from the given string. + * + * @param typeString + * A <code>String</code> that represents the string to parse. + * + * @return A <code>CopyStatus</code> value that represents the copy status. + */ + public static CopyStatus parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("invalid".equals(typeString.toLowerCase(Locale.US))) { + return INVALID; + } + else if ("pending".equals(typeString.toLowerCase(Locale.US))) { + return PENDING; + } + else if ("success".equals(typeString.toLowerCase(Locale.US))) { + return SUCCESS; + } + else if ("aborted".equals(typeString.toLowerCase(Locale.US))) { + return ABORTED; + } + else if ("failed".equals(typeString.toLowerCase(Locale.US))) { + return FAILED; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java similarity index 87% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java index 52866696d5b62..c8dc1c17dff8e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPermissions.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPermissions.java @@ -19,7 +19,7 @@ /** * Specifies the set of possible permissions for a shared access policy. */ -public enum SharedAccessPermissions { +public enum SharedAccessBlobPermissions { /** * Specifies Read access granted. */ @@ -48,8 +48,8 @@ public enum SharedAccessPermissions { * @return A <code>java.util.EnumSet</code> object that contains the <code>SharedAccessPermissions</code> values * corresponding to the specified byte value. */ - protected static EnumSet<SharedAccessPermissions> fromByte(final byte value) { - final EnumSet<SharedAccessPermissions> retSet = EnumSet.noneOf(SharedAccessPermissions.class); + protected static EnumSet<SharedAccessBlobPermissions> fromByte(final byte value) { + final EnumSet<SharedAccessBlobPermissions> retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); if (value == READ.value) { retSet.add(READ); @@ -79,7 +79,7 @@ protected static EnumSet<SharedAccessPermissions> fromByte(final byte value) { * @param val * The value being assigned. */ - SharedAccessPermissions(final byte val) { + SharedAccessBlobPermissions(final byte val) { this.value = val; } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java similarity index 77% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java index 3b04f396ffa63..937b558ab4c6b 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessPolicy.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessBlobPolicy.java @@ -23,7 +23,7 @@ * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access * signature. */ -public final class SharedAccessPolicy { +public final class SharedAccessBlobPolicy { /** * Assigns shared access permissions using the specified permissions string. @@ -39,26 +39,26 @@ public final class SharedAccessPolicy { * <li><code>w</code>: Write access.</li> * </ul> * - * @return A <code>java.util.EnumSet</code> object that contains {@link SharedAccessPermissions} values that + * @return A <code>java.util.EnumSet</code> object that contains {@link SharedAccessBlobPermissions} values that * represents the set of shared access permissions. */ - public static EnumSet<SharedAccessPermissions> permissionsFromString(final String value) { + public static EnumSet<SharedAccessBlobPermissions> permissionsFromString(final String value) { final char[] chars = value.toCharArray(); - final EnumSet<SharedAccessPermissions> retSet = EnumSet.noneOf(SharedAccessPermissions.class); + final EnumSet<SharedAccessBlobPermissions> retSet = EnumSet.noneOf(SharedAccessBlobPermissions.class); for (final char c : chars) { switch (c) { case 'r': - retSet.add(SharedAccessPermissions.READ); + retSet.add(SharedAccessBlobPermissions.READ); break; case 'w': - retSet.add(SharedAccessPermissions.WRITE); + retSet.add(SharedAccessBlobPermissions.WRITE); break; case 'd': - retSet.add(SharedAccessPermissions.DELETE); + retSet.add(SharedAccessBlobPermissions.DELETE); break; case 'l': - retSet.add(SharedAccessPermissions.LIST); + retSet.add(SharedAccessBlobPermissions.LIST); break; default: throw new IllegalArgumentException("value"); @@ -72,12 +72,12 @@ public static EnumSet<SharedAccessPermissions> permissionsFromString(final Strin * Converts the permissions specified for the shared access policy to a string. * * @param permissions - * A {@link SharedAccessPermissions} object that represents the shared access permissions. + * A {@link SharedAccessBlobPermissions} object that represents the shared access permissions. * * @return A <code>String</code> that represents the shared access permissions in the "rwdl" format, which is - * described at {@link SharedAccessPolicy#permissionsFromString}. + * described at {@link SharedAccessBlobPolicy#permissionsFromString}. */ - public static String permissionsToString(final EnumSet<SharedAccessPermissions> permissions) { + public static String permissionsToString(final EnumSet<SharedAccessBlobPermissions> permissions) { if (permissions == null) { return Constants.EMPTY_STRING; } @@ -85,19 +85,19 @@ public static String permissionsToString(final EnumSet<SharedAccessPermissions> // The service supports a fixed order => rwdl final StringBuilder builder = new StringBuilder(); - if (permissions.contains(SharedAccessPermissions.READ)) { + if (permissions.contains(SharedAccessBlobPermissions.READ)) { builder.append("r"); } - if (permissions.contains(SharedAccessPermissions.WRITE)) { + if (permissions.contains(SharedAccessBlobPermissions.WRITE)) { builder.append("w"); } - if (permissions.contains(SharedAccessPermissions.DELETE)) { + if (permissions.contains(SharedAccessBlobPermissions.DELETE)) { builder.append("d"); } - if (permissions.contains(SharedAccessPermissions.LIST)) { + if (permissions.contains(SharedAccessBlobPermissions.LIST)) { builder.append("l"); } @@ -107,7 +107,7 @@ public static String permissionsToString(final EnumSet<SharedAccessPermissions> /** * The permissions for a shared access signature associated with this shared access policy. */ - private EnumSet<SharedAccessPermissions> permissions; + private EnumSet<SharedAccessBlobPermissions> permissions; /** * The expiry time for a shared access signature associated with this shared access policy. @@ -122,14 +122,14 @@ public static String permissionsToString(final EnumSet<SharedAccessPermissions> /** * Creates an instance of the <code>SharedAccessPolicy</code> class. * */ - public SharedAccessPolicy() { + public SharedAccessBlobPolicy() { // Empty Default Ctor } /** * @return the permissions */ - public EnumSet<SharedAccessPermissions> getPermissions() { + public EnumSet<SharedAccessBlobPermissions> getPermissions() { return this.permissions; } @@ -151,7 +151,7 @@ public Date getSharedAccessStartTime() { * @param permissions * the permissions to set */ - public void setPermissions(final EnumSet<SharedAccessPermissions> permissions) { + public void setPermissions(final EnumSet<SharedAccessBlobPermissions> permissions) { this.permissions = permissions; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java deleted file mode 100644 index 98001ef8d378f..0000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/SharedAccessSignatureHelper.java +++ /dev/null @@ -1,248 +0,0 @@ -/** - * Copyright 2011 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.blob.client; - -import java.security.InvalidKeyException; -import java.util.HashMap; -import java.util.Map.Entry; - -import com.microsoft.windowsazure.services.core.storage.Constants; -import com.microsoft.windowsazure.services.core.storage.OperationContext; -import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; -import com.microsoft.windowsazure.services.core.storage.StorageException; -import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; -import com.microsoft.windowsazure.services.core.storage.utils.Utility; - -/** - * RESERVED FOR INTERNAL USE. Contains helper methods for implementing shared access signatures. - */ -final class SharedAccessSignatureHelper { - /** - * Get the complete query builder for creating the Shared Access Signature query. - * - * @param policy - * The shared access policy to hash. - * @param groupPolicyIdentifier - * An optional identifier for the policy. - * @param resourceType - * Either "b" for blobs or "c" for containers. - * @param signature - * The signature to use. - * @return The finished query builder - * @throws IllegalArgumentException - * @throws StorageException - */ - protected static UriQueryBuilder generateSharedAccessSignature(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, final String resourceType, final String signature) - throws StorageException { - Utility.assertNotNullOrEmpty("resourceType", resourceType); - Utility.assertNotNull("signature", signature); - - final UriQueryBuilder builder = new UriQueryBuilder(); - if (policy != null) { - String permissions = SharedAccessPolicy.permissionsToString(policy.getPermissions()); - - if (Utility.isNullOrEmpty(permissions)) { - permissions = null; - } - - final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); - if (!Utility.isNullOrEmpty(startString)) { - builder.add(BlobConstants.QueryConstants.SIGNED_START, startString); - } - - final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); - if (!Utility.isNullOrEmpty(stopString)) { - builder.add(BlobConstants.QueryConstants.SIGNED_EXPIRY, stopString); - } - - if (!Utility.isNullOrEmpty(permissions)) { - builder.add(BlobConstants.QueryConstants.SIGNED_PERMISSIONS, permissions); - } - } - - builder.add(BlobConstants.QueryConstants.SIGNED_RESOURCE, resourceType); - - if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { - builder.add(BlobConstants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); - } - - if (!Utility.isNullOrEmpty(signature)) { - builder.add(BlobConstants.QueryConstants.SIGNATURE, signature); - } - - return builder; - } - - /** - * Get the signature hash embedded inside the Shared Access Signature. - * - * @param policy - * The shared access policy to hash. - * @param groupPolicyIdentifier - * An optional identifier for the policy. - * @param resourceName - * the resource name. - * @param client - * the CloudBlobClient associated with the object. - * @param opContext - * an object used to track the execution of the operation - * @return the signature hash embedded inside the Shared Access Signature. - * @throws InvalidKeyException - * @throws StorageException - */ - protected static String generateSharedAccessSignatureHash(final SharedAccessPolicy policy, - final String groupPolicyIdentifier, final String resourceName, final CloudBlobClient client, - final OperationContext opContext) throws InvalidKeyException, StorageException { - Utility.assertNotNullOrEmpty("resourceName", resourceName); - Utility.assertNotNull("client", client); - - String stringToSign = null; - - if (policy == null) { - // Revokable access - Utility.assertNotNullOrEmpty("groupPolicyIdentifier", groupPolicyIdentifier); - stringToSign = String.format("%s\n%s\n%s\n%s\n%s", Constants.EMPTY_STRING, Constants.EMPTY_STRING, - Constants.EMPTY_STRING, resourceName, groupPolicyIdentifier); - } - else { - // Non Revokable access - if (policy.getSharedAccessExpiryTime() == null) { - throw new IllegalArgumentException("Policy Expiry time is mandatory and cannot be null"); - } - - if (policy.getPermissions() == null) { - throw new IllegalArgumentException("Policy permissions are mandatory and cannot be null"); - } - - stringToSign = String.format("%s\n%s\n%s\n%s\n%s", - SharedAccessPolicy.permissionsToString(policy.getPermissions()), - Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()), - Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()), resourceName, - groupPolicyIdentifier == null ? Constants.EMPTY_STRING : groupPolicyIdentifier); - } - - stringToSign = Utility.safeDecode(stringToSign); - final String signature = client.getCredentials().computeHmac256(stringToSign, opContext); - - // add logging - return signature; - } - - /** - * Parses the query parameters and populates a StorageCredentialsSharedAccessSignature object if one is present. - * - * @param queryParams - * the parameters to parse - * @return the StorageCredentialsSharedAccessSignature if one is present, otherwise null - * @throws IllegalArgumentException - * @throws StorageException - * an exception representing any error which occurred during the operation. - */ - protected static StorageCredentialsSharedAccessSignature parseQuery(final HashMap<String, String[]> queryParams) - throws StorageException { - String signature = null; - String signedStart = null; - String signedExpiry = null; - String signedResource = null; - String sigendPermissions = null; - String signedIdentifier = null; - String signedVersion = null; - - boolean sasParameterFound = false; - - StorageCredentialsSharedAccessSignature credentials = null; - - for (final Entry<String, String[]> entry : queryParams.entrySet()) { - final String lowerKey = entry.getKey().toLowerCase(Utility.LOCALE_US); - - if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_START)) { - signedStart = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_EXPIRY)) { - signedExpiry = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_PERMISSIONS)) { - sigendPermissions = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_RESOURCE)) { - signedResource = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_IDENTIFIER)) { - signedIdentifier = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNATURE)) { - signature = entry.getValue()[0]; - sasParameterFound = true; - } - else if (lowerKey.equals(BlobConstants.QueryConstants.SIGNED_VERSION)) { - signedVersion = entry.getValue()[0]; - sasParameterFound = true; - } - } - - if (sasParameterFound) { - if (signature == null || signedResource == null) { - final String errorMessage = "Missing mandatory parameters for valid Shared Access Signature"; - throw new IllegalArgumentException(errorMessage); - } - - final UriQueryBuilder builder = new UriQueryBuilder(); - - if (!Utility.isNullOrEmpty(signedStart)) { - builder.add(BlobConstants.QueryConstants.SIGNED_START, signedStart); - } - - if (!Utility.isNullOrEmpty(signedExpiry)) { - builder.add(BlobConstants.QueryConstants.SIGNED_EXPIRY, signedExpiry); - } - - if (!Utility.isNullOrEmpty(sigendPermissions)) { - builder.add(BlobConstants.QueryConstants.SIGNED_PERMISSIONS, sigendPermissions); - } - - builder.add(BlobConstants.QueryConstants.SIGNED_RESOURCE, signedResource); - - if (!Utility.isNullOrEmpty(signedIdentifier)) { - builder.add(BlobConstants.QueryConstants.SIGNED_IDENTIFIER, signedIdentifier); - } - - if (!Utility.isNullOrEmpty(signedVersion)) { - builder.add(BlobConstants.QueryConstants.SIGNED_VERSION, signedVersion); - } - - if (!Utility.isNullOrEmpty(signature)) { - builder.add(BlobConstants.QueryConstants.SIGNATURE, signature); - } - - final String token = builder.toString(); - credentials = new StorageCredentialsSharedAccessSignature(token); - } - - return credentials; - } - - /** - * Private Default Ctor. - */ - private SharedAccessSignatureHelper() { - // No op - } -} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java new file mode 100644 index 0000000000000..26cefa304d708 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/core/storage/SharedAccessSignatureHelper.java @@ -0,0 +1,497 @@ +/** + * Copyright 2011 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.blob.core.storage; + +import java.security.InvalidKeyException; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; + +import com.microsoft.windowsazure.services.blob.client.SharedAccessBlobPolicy; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.ServiceClient; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; +import com.microsoft.windowsazure.services.queue.client.SharedAccessQueuePolicy; +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePolicy; + +/** + * RESERVED FOR INTERNAL USE. Contains helper methods for implementing shared access signatures. + */ +public class SharedAccessSignatureHelper { + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceType + * Either "b" for blobs or "c" for containers. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessBlobPolicy policy, + final String groupPolicyIdentifier, final String resourceType, final String signature) + throws StorageException { + Utility.assertNotNullOrEmpty("resourceType", resourceType); + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (policy != null) { + String permissions = SharedAccessBlobPolicy.permissionsToString(policy.getPermissions()); + + if (Utility.isNullOrEmpty(permissions)) { + permissions = null; + } + + final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + } + + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, resourceType); + + if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessQueuePolicy policy, + final String groupPolicyIdentifier, final String signature) throws StorageException { + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (policy != null) { + String permissions = SharedAccessQueuePolicy.permissionsToString(policy.getPermissions()); + + if (Utility.isNullOrEmpty(permissions)) { + permissions = null; + } + + final String startString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime()); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime()); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + } + + if (!Utility.isNullOrEmpty(groupPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, groupPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param signature + * The signature to use. + * @return The finished query builder + * @throws IllegalArgumentException + * @throws StorageException + */ + public static UriQueryBuilder generateSharedAccessSignature(final String permissions, final Date startTime, + final Date expiryTime, final String startPatitionKey, final String startRowKey, + final String endPatitionKey, final String endRowKey, final String accessPolicyIdentifier, + final String resourceType, final String tableName, final String signature, final String accountKeyName) + throws StorageException { + Utility.assertNotNull("signature", signature); + + final UriQueryBuilder builder = new UriQueryBuilder(); + + builder.add(Constants.QueryConstants.SIGNED_VERSION, Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (!Utility.isNullOrEmpty(permissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, permissions); + } + + final String startString = Utility.getUTCTimeOrEmpty(startTime); + if (!Utility.isNullOrEmpty(startString)) { + builder.add(Constants.QueryConstants.SIGNED_START, startString); + } + + final String stopString = Utility.getUTCTimeOrEmpty(expiryTime); + if (!Utility.isNullOrEmpty(stopString)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, stopString); + } + + if (!Utility.isNullOrEmpty(startPatitionKey)) { + builder.add(Constants.QueryConstants.START_PARTITION_KEY, startPatitionKey); + } + + if (!Utility.isNullOrEmpty(startRowKey)) { + builder.add(Constants.QueryConstants.START_ROW_KEY, startRowKey); + } + + if (!Utility.isNullOrEmpty(endPatitionKey)) { + builder.add(Constants.QueryConstants.END_PARTITION_KEY, endPatitionKey); + } + + if (!Utility.isNullOrEmpty(endRowKey)) { + builder.add(Constants.QueryConstants.END_ROW_KEY, endRowKey); + } + + if (!Utility.isNullOrEmpty(accessPolicyIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, accessPolicyIdentifier); + } + + if (!Utility.isNullOrEmpty(resourceType)) { + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, resourceType); + } + + if (!Utility.isNullOrEmpty(tableName)) { + builder.add(Constants.QueryConstants.SAS_TABLE_NAME, tableName); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + if (!Utility.isNullOrEmpty(accountKeyName)) { + builder.add(Constants.QueryConstants.SIGNED_KEY, accountKeyName); + } + + return builder; + } + + /** + * Get the complete query builder for creating the Shared Access Signature query. + */ + public static UriQueryBuilder generateSharedAccessSignature(final SharedAccessTablePolicy policy, + final String startPartitionKey, final String startRowKey, final String endPartitionKey, + final String endRowKey, final String accessPolicyIdentifier, final String tableName, + final String signature, final String accountKeyName) throws StorageException { + + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessTablePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignature(permissionString, startTime, expiryTime, startPartitionKey, startRowKey, + endPartitionKey, endRowKey, accessPolicyIdentifier, null, tableName, signature, accountKeyName); + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for blob service. + * + * @param policy + * The shared access policy to hash. + * @param accessPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final SharedAccessBlobPolicy policy, + final String accessPolicyIdentifier, final String resourceName, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessBlobPolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, false, null, null, null, null, client, opContext); + + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for queue service. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + + public static String generateSharedAccessSignatureHash(final SharedAccessQueuePolicy policy, + final String accessPolicyIdentifier, final String resourceName, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessQueuePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, false, null, null, null, null, client, opContext); + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for all storage service. + * + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final String permissions, final Date startTime, + final Date expiryTime, final String resourceName, final String accessPolicyIdentifier, + final boolean useTableSas, final String startPatitionKey, final String startRowKey, + final String endPatitionKey, final String endRowKey, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + Utility.assertNotNullOrEmpty("resourceName", resourceName); + Utility.assertNotNull("client", client); + + String stringToSign = String.format("%s\n%s\n%s\n%s\n%s\n%s", permissions == null ? Constants.EMPTY_STRING + : permissions, Utility.getUTCTimeOrEmpty(startTime), Utility.getUTCTimeOrEmpty(expiryTime), + resourceName, accessPolicyIdentifier == null ? Constants.EMPTY_STRING : accessPolicyIdentifier, + Constants.HeaderConstants.TARGET_STORAGE_VERSION); + + if (useTableSas) { + stringToSign = String.format("%s\n%s\n%s\n%s\n%s", stringToSign, + startPatitionKey == null ? Constants.EMPTY_STRING : startPatitionKey, + startRowKey == null ? Constants.EMPTY_STRING : startRowKey, + endPatitionKey == null ? Constants.EMPTY_STRING : endPatitionKey, + endRowKey == null ? Constants.EMPTY_STRING : endRowKey); + } + + stringToSign = Utility.safeDecode(stringToSign); + final String signature = client.getCredentials().computeHmac256(stringToSign, opContext); + + // add logging + return signature; + } + + /** + * Get the signature hash embedded inside the Shared Access Signature for blob service. + * + * @param policy + * The shared access policy to hash. + * @param groupPolicyIdentifier + * An optional identifier for the policy. + * @param resourceName + * the resource name. + * @param client + * the ServiceClient associated with the object. + * @param opContext + * an object used to track the execution of the operation + * @return the signature hash embedded inside the Shared Access Signature. + * @throws InvalidKeyException + * @throws StorageException + */ + public static String generateSharedAccessSignatureHash(final SharedAccessTablePolicy policy, + final String accessPolicyIdentifier, final String resourceName, final String startPartitionKey, + final String startRowKey, final String endPartitionKey, final String endRowKey, final ServiceClient client, + final OperationContext opContext) throws InvalidKeyException, StorageException { + String permissionString = null; + Date startTime = null; + Date expiryTime = null; + + if (policy != null) { + permissionString = SharedAccessTablePolicy.permissionsToString(policy.getPermissions()); + startTime = policy.getSharedAccessStartTime(); + expiryTime = policy.getSharedAccessExpiryTime(); + } + + return generateSharedAccessSignatureHash(permissionString, startTime, expiryTime, resourceName, + accessPolicyIdentifier, true, startPartitionKey, startRowKey, endPartitionKey, endRowKey, client, + opContext); + + } + + /** + * Parses the query parameters and populates a StorageCredentialsSharedAccessSignature object if one is present. + * + * @param queryParams + * the parameters to parse + * @return the StorageCredentialsSharedAccessSignature if one is present, otherwise null + * @throws IllegalArgumentException + * @throws StorageException + * an exception representing any error which occurred during the operation. + */ + public static StorageCredentialsSharedAccessSignature parseQuery(final HashMap<String, String[]> queryParams) + throws StorageException { + String signature = null; + String signedStart = null; + String signedExpiry = null; + String signedResource = null; + String sigendPermissions = null; + String signedIdentifier = null; + String signedVersion = null; + + boolean sasParameterFound = false; + + StorageCredentialsSharedAccessSignature credentials = null; + + for (final Entry<String, String[]> entry : queryParams.entrySet()) { + final String lowerKey = entry.getKey().toLowerCase(Utility.LOCALE_US); + + if (lowerKey.equals(Constants.QueryConstants.SIGNED_START)) { + signedStart = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_EXPIRY)) { + signedExpiry = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_PERMISSIONS)) { + sigendPermissions = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_RESOURCE)) { + signedResource = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_IDENTIFIER)) { + signedIdentifier = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNATURE)) { + signature = entry.getValue()[0]; + sasParameterFound = true; + } + else if (lowerKey.equals(Constants.QueryConstants.SIGNED_VERSION)) { + signedVersion = entry.getValue()[0]; + sasParameterFound = true; + } + } + + if (sasParameterFound) { + if (signature == null) { + final String errorMessage = "Missing mandatory parameters for valid Shared Access Signature"; + throw new IllegalArgumentException(errorMessage); + } + + final UriQueryBuilder builder = new UriQueryBuilder(); + + if (!Utility.isNullOrEmpty(signedStart)) { + builder.add(Constants.QueryConstants.SIGNED_START, signedStart); + } + + if (!Utility.isNullOrEmpty(signedExpiry)) { + builder.add(Constants.QueryConstants.SIGNED_EXPIRY, signedExpiry); + } + + if (!Utility.isNullOrEmpty(sigendPermissions)) { + builder.add(Constants.QueryConstants.SIGNED_PERMISSIONS, sigendPermissions); + } + + if (!Utility.isNullOrEmpty(signedResource)) { + builder.add(Constants.QueryConstants.SIGNED_RESOURCE, signedResource); + } + + if (!Utility.isNullOrEmpty(signedIdentifier)) { + builder.add(Constants.QueryConstants.SIGNED_IDENTIFIER, signedIdentifier); + } + + if (!Utility.isNullOrEmpty(signedVersion)) { + builder.add(Constants.QueryConstants.SIGNED_VERSION, signedVersion); + } + + if (!Utility.isNullOrEmpty(signature)) { + builder.add(Constants.QueryConstants.SIGNATURE, signature); + } + + final String token = builder.toString(); + credentials = new StorageCredentialsSharedAccessSignature(token); + } + + return credentials; + } + + /** + * Private Default Ctor. + */ + private SharedAccessSignatureHelper() { + // No op + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java similarity index 64% rename from microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java rename to microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java index d651b14ac6c78..9e513711cdb31 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/client/AccessPolicyResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/AccessPolicyResponseBase.java @@ -12,7 +12,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.microsoft.windowsazure.services.blob.client; +package com.microsoft.windowsazure.services.core.storage; import java.io.InputStream; import java.text.ParseException; @@ -22,13 +22,12 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; -import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.utils.Utility; /** * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. */ -final class AccessPolicyResponse { +public abstract class AccessPolicyResponseBase<T> { /** * Holds a flag indicating if the response has been parsed or not. */ @@ -37,7 +36,7 @@ final class AccessPolicyResponse { /** * Holds the Hashmap of policies parsed from the stream */ - private final HashMap<String, SharedAccessPolicy> policies = new HashMap<String, SharedAccessPolicy>(); + private final HashMap<String, T> policies = new HashMap<String, T>(); /** * Holds a reference to the input stream to read from. @@ -50,7 +49,7 @@ final class AccessPolicyResponse { * @param stream * the input stream to read error details from. */ - public AccessPolicyResponse(final InputStream stream) { + public AccessPolicyResponseBase(final InputStream stream) { this.streamRef = stream; } @@ -63,7 +62,7 @@ public AccessPolicyResponse(final InputStream stream) { * @throws ParseException * if a date is incorrectly encoded in the stream */ - public HashMap<String, SharedAccessPolicy> getAccessIdentifiers() throws XMLStreamException, ParseException { + public HashMap<String, T> getAccessIdentifiers() throws XMLStreamException, ParseException { if (!this.isParsed) { this.parseResponse(); } @@ -93,12 +92,11 @@ public void parseResponse() throws XMLStreamException, ParseException { if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { final String name = xmlr.getName().toString(); - if (eventType == XMLStreamConstants.START_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { this.readPolicies(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { break; } } @@ -123,7 +121,7 @@ else if (eventType == XMLStreamConstants.END_DOCUMENT) { private void readPolicies(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { int eventType = xmlr.getEventType(); - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.SIGNED_IDENTIFIERS_ELEMENT); while (xmlr.hasNext()) { eventType = xmlr.next(); @@ -131,62 +129,17 @@ private void readPolicies(final XMLStreamReader xmlr) throws XMLStreamException, if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { final String name = xmlr.getName().toString(); - if (eventType == XMLStreamConstants.START_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIER_ELEMENT)) { + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.SIGNED_IDENTIFIER_ELEMENT)) { this.readSignedIdentifier(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIERS_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIERS_ELEMENT)) { break; } } } - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIERS_ELEMENT); - } - - /** - * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. - * - * @param xmlr - * the XMLStreamReader object - * @throws XMLStreamException - * if there is a parsing exception - * @throws ParseException - * if a date value is not correctly encoded - */ - private SharedAccessPolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { - int eventType = xmlr.getEventType(); - - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.ACCESS_POLICY); - final SharedAccessPolicy retPolicy = new SharedAccessPolicy(); - - while (xmlr.hasNext()) { - eventType = xmlr.next(); - - if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { - final String name = xmlr.getName().toString(); - - if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.PERMISSION)) { - retPolicy.setPermissions(SharedAccessPolicy.permissionsFromString(Utility.readElementFromXMLReader( - xmlr, BlobConstants.PERMISSION))); - } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.START)) { - final String tempString = Utility.readElementFromXMLReader(xmlr, BlobConstants.START); - retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); - } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.EXPIRY)) { - final String tempString = Utility.readElementFromXMLReader(xmlr, BlobConstants.EXPIRY); - retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); - } - else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstants.ACCESS_POLICY)) { - break; - } - } - } - - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.ACCESS_POLICY); - return retPolicy; + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.SIGNED_IDENTIFIERS_ELEMENT); } /** @@ -201,10 +154,10 @@ else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(BlobConstant */ private void readSignedIdentifier(final XMLStreamReader xmlr) throws XMLStreamException, ParseException { int eventType = xmlr.getEventType(); - xmlr.require(XMLStreamConstants.START_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.SIGNED_IDENTIFIER_ELEMENT); String id = null; - SharedAccessPolicy policy = null; + T policy = null; while (xmlr.hasNext()) { eventType = xmlr.next(); @@ -214,17 +167,29 @@ private void readSignedIdentifier(final XMLStreamReader xmlr) throws XMLStreamEx if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.ID)) { id = Utility.readElementFromXMLReader(xmlr, Constants.ID); } - else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(BlobConstants.ACCESS_POLICY)) { + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { policy = this.readPolicyFromXML(xmlr); } else if (eventType == XMLStreamConstants.END_ELEMENT - && name.equals(BlobConstants.SIGNED_IDENTIFIER_ELEMENT)) { + && name.equals(Constants.SIGNED_IDENTIFIER_ELEMENT)) { this.policies.put(id, policy); break; } } } - xmlr.require(XMLStreamConstants.END_ELEMENT, null, BlobConstants.SIGNED_IDENTIFIER_ELEMENT); + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.SIGNED_IDENTIFIER_ELEMENT); } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + protected abstract T readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, ParseException; } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java index 7676efe058daa..ae1b15bf0b3de 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Constants.java @@ -202,6 +202,46 @@ public static class HeaderConstants { */ public static final String LEASE_STATUS = PREFIX_FOR_STORAGE_HEADER + "lease-status"; + /** + * The header that specifies lease state. + */ + public static final String LEASE_STATE = PREFIX_FOR_STORAGE_HEADER + "lease-state"; + + /** + * The header that specifies lease duration. + */ + public static final String LEASE_DURATION = PREFIX_FOR_STORAGE_HEADER + "lease-duration"; + + /** + * The header that specifies copy status. + */ + public static final String COPY_STATUS = PREFIX_FOR_STORAGE_HEADER + "copy-status"; + + /** + * The header that specifies copy progress. + */ + public static final String COPY_PROGRESS = PREFIX_FOR_STORAGE_HEADER + "copy-progress"; + + /** + * The header that specifies copy status description. + */ + public static final String COPY_STATUS_DESCRIPTION = PREFIX_FOR_STORAGE_HEADER + "copy-status-description"; + + /** + * The header that specifies copy id. + */ + public static final String COPY_ID = PREFIX_FOR_STORAGE_HEADER + "copy-id"; + + /** + * The header that specifies copy source. + */ + public static final String COPY_SOURCE = PREFIX_FOR_STORAGE_HEADER + "copy-source"; + + /** + * The header that specifies copy completion time. + */ + public static final String COPY_COMPLETION_TIME = PREFIX_FOR_STORAGE_HEADER + "copy-completion-time"; + /** * The header prefix for metadata. */ @@ -272,7 +312,7 @@ public static class HeaderConstants { /** * The current storage version header value. */ - public static final String TARGET_STORAGE_VERSION = "2011-08-18"; + public static final String TARGET_STORAGE_VERSION = "2012-02-12"; /** * The UserAgent header. @@ -290,6 +330,81 @@ public static class HeaderConstants { public static final String USER_AGENT_VERSION = "Client v0.1.2"; } + /** + * Defines constants for use with query strings. + */ + public static class QueryConstants { + /** + * The query component for the SAS signature. + */ + public static final String SIGNATURE = "sig"; + + /** + * The query component for the signed SAS expiry time. + */ + public static final String SIGNED_EXPIRY = "se"; + + /** + * The query component for the signed SAS identifier. + */ + public static final String SIGNED_IDENTIFIER = "si"; + + /** + * The query component for the signed SAS permissions. + */ + public static final String SIGNED_PERMISSIONS = "sp"; + + /** + * The query component for the signed SAS resource. + */ + public static final String SIGNED_RESOURCE = "sr"; + + /** + * The query component for the signed SAS start time. + */ + public static final String SIGNED_START = "st"; + + /** + * The query component for the SAS start partition key. + */ + public static final String START_PARTITION_KEY = "spk"; + + /** + * The query component for the SAS start row key. + */ + public static final String START_ROW_KEY = "srk"; + + /** + * The query component for the SAS end partition key. + */ + public static final String END_PARTITION_KEY = "epk"; + + /** + * The query component for the SAS end row key. + */ + public static final String END_ROW_KEY = "erk"; + + /** + * The query component for the SAS table name. + */ + public static final String SAS_TABLE_NAME = "tn"; + + /** + * The query component for the signing SAS key. + */ + public static final String SIGNED_KEY = "sk"; + + /** + * The query component for the signed SAS version. + */ + public static final String SIGNED_VERSION = "sv"; + + /** + * The query component for snapshot time. + */ + public static final String SNAPSHOT = "snapshot"; + } + /** * The master Windows Azure Storage header prefix. */ @@ -405,6 +520,46 @@ public static class HeaderConstants { */ public static final String LEASE_STATUS_ELEMENT = "LeaseStatus"; + /** + * XML element for the lease state. + */ + public static final String LEASE_STATE_ELEMENT = "LeaseState"; + + /** + * XML element for the lease duration. + */ + public static final String LEASE_DURATION_ELEMENT = "LeaseDuration"; + + /** + * XML element for the copy id. + */ + public static final String COPY_ID_ELEMENT = "CopyId"; + + /** + * XML element for the copy status. + */ + public static final String COPY_STATUS_ELEMENT = "CopyStatus"; + + /** + * XML element for the copy source . + */ + public static final String COPY_SOURCE_ELEMENT = "CopySource"; + + /** + * XML element for the copy progress. + */ + public static final String COPY_PROGRESS_ELEMENT = "CopyProgress"; + + /** + * XML element for the copy completion time. + */ + public static final String COPY_COMPLETION_TIME_ELEMENT = "CopyCompletionTime"; + + /** + * XML element for the copy status description. + */ + public static final String COPY_STATUS_DESCRIPTION_ELEMENT = "CopyStatusDescription"; + /** * Constant signaling the resource is locked. */ @@ -466,6 +621,41 @@ public static class HeaderConstants { */ public static final String URL_ELEMENT = "Url"; + /** + * XML element for a signed identifier. + */ + public static final String SIGNED_IDENTIFIER_ELEMENT = "SignedIdentifier"; + + /** + * XML element for signed identifiers. + */ + public static final String SIGNED_IDENTIFIERS_ELEMENT = "SignedIdentifiers"; + + /** + * XML element for an access policy. + */ + public static final String ACCESS_POLICY = "AccessPolicy"; + + /** + * Maximum number of shared access policy identifiers supported by server. + */ + public static final int MAX_SHARED_ACCESS_POLICY_IDENTIFIERS = 5; + + /** + * XML element for the start time of an access policy. + */ + public static final String START = "Start"; + + /** + * XML element for the end time of an access policy. + */ + public static final String EXPIRY = "Expiry"; + + /** + * XML element for the permission of an access policy. + */ + public static final String PERMISSION = "Permission"; + /** * Private Default Ctor */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java index e506895cb8511..705075775610e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/Credentials.java @@ -33,6 +33,16 @@ public final class Credentials { */ private final StorageKey key; + /** + * Stores the name of the access key to be used when signing the request. + */ + private String keyName; + + /** + * Stores the account name whose key is used to sign requests. + */ + private String signingAccountName; + /** * Creates an instance of the <code>Credentials</code> class, using the specified storage account name and access * key; the specified access key is in the form of a byte array. @@ -54,6 +64,7 @@ public Credentials(final String accountName, final byte[] key) { this.accountName = accountName; this.key = new StorageKey(key); + this.signingAccountName = accountName; } /** @@ -97,6 +108,22 @@ public String getAccountName() { return this.accountName; } + /** + * Returns the account name whose key is used to sign requests. + * Internal use only. + */ + public String getSigningAccountName() { + return this.signingAccountName; + } + + /** + * Returns the name of the access key to be used when signing the request. + * Internal use only. + */ + public String getKeyName() { + return this.keyName; + } + /** * Returns the access key to be used in signing the request. * @@ -115,4 +142,24 @@ public StorageKey getKey() { protected void setAccountName(final String accountName) { this.accountName = accountName; } + + /** + * Sets the account name whose key is used to sign requests. + * + * @param signingAccountName + * A <code>String</code> that represents the account name whose key is used to sign requests. + */ + protected void setSigningAccountName(final String signingAccountName) { + this.signingAccountName = signingAccountName; + } + + /** + * Sets the name of the access key to be used when signing the request. + * + * @param keyName + * A <code>String</code> that represents the name of the access key to be used when signing the request. + */ + protected void setKeyName(final String keyName) { + this.keyName = keyName; + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java new file mode 100644 index 0000000000000..2c807c8dd57ff --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseDuration.java @@ -0,0 +1,62 @@ +/** + * Copyright 2011 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.core.storage; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * The lease duration of a resource. + */ +public enum LeaseDuration { + /** + * The lease duration is not specified. + */ + UNSPECIFIED, + + /** + * The lease duration is finite. + */ + FIXED, + + /** + * The lease duration is infinite. + */ + INFINITE; + + /** + * Parses a lease duration from the given string. + * + * @param typeString + * A <code>String</code> that represents the string to parse. + * + * @return A <code>LeaseStatus</code> value that represents the lease status. + */ + public static LeaseDuration parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("fixed".equals(typeString.toLowerCase(Locale.US))) { + return FIXED; + } + else if ("infinite".equals(typeString.toLowerCase(Locale.US))) { + return INFINITE; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java new file mode 100644 index 0000000000000..c2c7f9a79e3ff --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseState.java @@ -0,0 +1,86 @@ +/** + * Copyright 2011 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.core.storage; + +import java.util.Locale; + +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * he lease state of a resource. + */ +public enum LeaseState { + /** + * The lease state is not specified. + */ + UNSPECIFIED, + + /** + * The lease is in the Available state. + */ + AVAILABLE, + + /** + * The lease is in the Leased state. + */ + LEASED, + + /** + * The lease is in the Expired state. + */ + EXPIRED, + + /** + * The lease is in the Breaking state. + */ + BREAKING, + + /** + * The lease is in the Broken state. + */ + BROKEN; + + /** + * Parses a lease status from the given string. + * + * @param typeString + * A <code>String</code> that represents the string to parse. + * + * @return A <code>LeaseStatus</code> value that represents the lease status. + */ + public static LeaseState parse(final String typeString) { + if (Utility.isNullOrEmpty(typeString)) { + return UNSPECIFIED; + } + else if ("available".equals(typeString.toLowerCase(Locale.US))) { + return AVAILABLE; + } + else if ("locked".equals(typeString.toLowerCase(Locale.US))) { + return LEASED; + } + else if ("expired".equals(typeString.toLowerCase(Locale.US))) { + return EXPIRED; + } + else if ("breaking".equals(typeString.toLowerCase(Locale.US))) { + return BREAKING; + } + else if ("broken".equals(typeString.toLowerCase(Locale.US))) { + return BROKEN; + } + else { + return UNSPECIFIED; + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java index 7a1856142f5e5..b82ab088f0bc3 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/LeaseStatus.java @@ -26,19 +26,19 @@ */ public enum LeaseStatus { /** - * Specifies the blob is locked for exclusive-write access. + * Specifies the lease status is not specified. */ - LOCKED, + UNSPECIFIED, /** - * Specifies the blob is available to be locked for exclusive-write access. + * Specifies the blob is locked for exclusive-write access. */ - UNLOCKED, + LOCKED, /** - * Specifies the lease status is not specified. + * Specifies the blob is available to be locked for exclusive-write access. */ - UNSPECIFIED; + UNLOCKED; /** * Parses a lease status from the given string. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java index fba8df30bfa20..3406fc76c38b6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageCredentialsAccountAndKey.java @@ -195,6 +195,22 @@ public String getAccountName() { return this.credentials.getAccountName(); } + /** + * Internal usage. + * Gets the name of the key used by these credentials. + */ + public String getAccountKeyName() { + return this.credentials.getKeyName(); + } + + /** + * Internal usage. + * Sets the account name that owns the key to use when signing requests. + */ + public void setSigningAccountName(final String signingAccountName) { + this.credentials.setSigningAccountName(signingAccountName); + } + /** * Returns the Base64-encoded key for the credentials. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java index b310204bc8ea4..2482119f82bfc 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/StorageErrorCode.java @@ -117,7 +117,22 @@ public enum StorageErrorCode { /** * A transport error occurred (server-side error). */ - TRANSPORT_ERROR(5); + TRANSPORT_ERROR(5), + + /** + * A lease is required to perform the operation. + */ + LEASE_ID_MISSING(21), + + /** + * The given lease ID does not match the current lease. + */ + LEASE_ID_MISMATCH(22), + + /** + * A lease ID was used when no lease currently is held. + */ + LEASE_NOT_PRESENT(23); /** * Returns the value of this enum. diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java index c9fdf722b6c41..7783f7e29548d 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/PathUtility.java @@ -196,12 +196,27 @@ public static String getCanonicalPathFromCredentials(final StorageCredentials cr * @throws IllegalArgumentException */ public static String getContainerNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { - return getContainerOrQueueNameFromUri(resourceAddress, usePathStyleUris, + return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid blob address '%s', missing container information", resourceAddress)); } /** - * Get the container or queue name from address from the URI. + * Get the table name from address from the URI. + * + * @param resourceAddress + * The table Uri. + * @param usePathStyleUris + * a value indicating if the address is a path style uri. + * @return table name from address from the URI. + * @throws IllegalArgumentException + */ + public static String getTableNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { + return getResourceNameFromUri(resourceAddress, usePathStyleUris, + String.format("Invalid table address '%s', missing table information", resourceAddress)); + } + + /** + * Get the container, queue or table name from address from the URI. * * @param resourceAddress * The queue Uri. @@ -210,7 +225,7 @@ public static String getContainerNameFromUri(final URI resourceAddress, final bo * @return container name from address from the URI. * @throws IllegalArgumentException */ - private static String getContainerOrQueueNameFromUri(final URI resourceAddress, final boolean usePathStyleUris, + private static String getResourceNameFromUri(final URI resourceAddress, final boolean usePathStyleUris, final String error) { Utility.assertNotNull("resourceAddress", resourceAddress); @@ -222,9 +237,9 @@ private static String getContainerOrQueueNameFromUri(final URI resourceAddress, throw new IllegalArgumentException(error); } - final String containerOrQueueName = usePathStyleUris ? pathSegments[2] : pathSegments[1]; + final String resourceName = usePathStyleUris ? pathSegments[2] : pathSegments[1]; - return Utility.trimEnd(containerOrQueueName, '/'); + return Utility.trimEnd(resourceName, '/'); } /** @@ -341,7 +356,7 @@ public static String getParentNameFromURI(final URI resourceAddress, final Strin * @throws IllegalArgumentException */ public static String getQueueNameFromUri(final URI resourceAddress, final boolean usePathStyleUris) { - return getContainerOrQueueNameFromUri(resourceAddress, usePathStyleUris, + return getResourceNameFromUri(resourceAddress, usePathStyleUris, String.format("Invalid queue URI '%s'.", resourceAddress)); } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java index 25611e73cf547..e47b64181ac97 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/BaseResponse.java @@ -16,6 +16,9 @@ import java.io.InputStream; import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.text.ParseException; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -23,10 +26,16 @@ import javax.xml.stream.XMLStreamException; +import com.microsoft.windowsazure.services.blob.client.CopyState; +import com.microsoft.windowsazure.services.blob.client.CopyStatus; import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.LeaseDuration; +import com.microsoft.windowsazure.services.core.storage.LeaseState; +import com.microsoft.windowsazure.services.core.storage.LeaseStatus; import com.microsoft.windowsazure.services.core.storage.OperationContext; import com.microsoft.windowsazure.services.core.storage.ServiceProperties; import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; /** * RESERVED FOR INTERNAL USE. The base response class for the protocol layer @@ -75,6 +84,96 @@ public static HashMap<String, String> getMetadata(final HttpURLConnection reques return getValuesByHeaderPrefix(request, Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA); } + /** + * Gets the LeaseStatus + * + * @param request + * The response from server. + * @return The Etag. + */ + public static LeaseStatus getLeaseStatus(final HttpURLConnection request) { + final String leaseStatus = request.getHeaderField(Constants.HeaderConstants.LEASE_STATUS); + if (!Utility.isNullOrEmpty(leaseStatus)) { + return LeaseStatus.parse(leaseStatus); + } + + return LeaseStatus.UNSPECIFIED; + } + + /** + * Gets the LeaseState + * + * @param request + * The response from server. + * @return The LeaseState. + */ + public static LeaseState getLeaseState(final HttpURLConnection request) { + final String leaseState = request.getHeaderField(Constants.HeaderConstants.LEASE_STATE); + if (!Utility.isNullOrEmpty(leaseState)) { + return LeaseState.parse(leaseState); + } + + return LeaseState.UNSPECIFIED; + } + + /** + * Gets the LeaseDuration + * + * @param request + * The response from server. + * @return The LeaseDuration. + */ + public static LeaseDuration getLeaseDuration(final HttpURLConnection request) { + final String leaseDuration = request.getHeaderField(Constants.HeaderConstants.LEASE_DURATION); + if (!Utility.isNullOrEmpty(leaseDuration)) { + return LeaseDuration.parse(leaseDuration); + } + + return LeaseDuration.UNSPECIFIED; + } + + /** + * Gets the copyState + * + * @param request + * The response from server. + * @return The CopyState. + * @throws URISyntaxException + * @throws ParseException + */ + public static CopyState getCopyState(final HttpURLConnection request) throws URISyntaxException, ParseException { + String copyStatusString = request.getHeaderField(Constants.HeaderConstants.COPY_STATUS); + if (!Utility.isNullOrEmpty(copyStatusString)) { + CopyState copyState = new CopyState(); + copyState.setStatus(CopyStatus.parse(copyStatusString)); + copyState.setCopyId(request.getHeaderField(Constants.HeaderConstants.COPY_ID)); + copyState.setStatusDescription(request.getHeaderField(Constants.HeaderConstants.COPY_STATUS_DESCRIPTION)); + + final String copyProgressString = request.getHeaderField(Constants.HeaderConstants.COPY_PROGRESS); + if (!Utility.isNullOrEmpty(copyProgressString)) { + String[] progressSequence = copyProgressString.split("/"); + copyState.setBytesCopied(Long.parseLong(progressSequence[0])); + copyState.setTotalBytes(Long.parseLong(progressSequence[1])); + } + + final String copySourceString = request.getHeaderField(Constants.HeaderConstants.COPY_SOURCE); + if (!Utility.isNullOrEmpty(copySourceString)) { + copyState.setSource(new URI(copySourceString)); + } + + final String copyCompletionTimeString = request + .getHeaderField(Constants.HeaderConstants.COPY_COMPLETION_TIME); + if (!Utility.isNullOrEmpty(copyCompletionTimeString)) { + copyState.setCompletionTime(Utility.parseRFC1123DateFromStringInGMT(copyCompletionTimeString)); + } + + return copyState; + } + else { + return null; + } + } + /** * Gets the request id. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java index 17fe0633547bc..d12785cbb8af5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/Canonicalizer.java @@ -326,11 +326,10 @@ protected static String getCanonicalizedResourceLite(final java.net.URL address, if (stringValue.length() > 0) { stringValue.append(","); } - stringValue.append(value); } - appendCanonicalizedElement(canonicalizedResource, stringValue.toString()); + canonicalizedResource.append(stringValue); } return canonicalizedResource.toString(); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java index e7bb775a2d71e..b17a657c029d5 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/storage/utils/implementation/LeaseAction.java @@ -39,7 +39,12 @@ public enum LeaseAction { /** * Break the lease. */ - BREAK; + BREAK, + + /** + * Change the lease. + */ + CHANGE; @Override public String toString() { @@ -52,6 +57,8 @@ public String toString() { return "Release"; case BREAK: return "Break"; + case CHANGE: + return "Change"; default: // Wont Happen, all possible values covered above. return Constants.EMPTY_STRING; diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java index 5e4ddf1ffdd7e..f77e7931062e0 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/CloudQueue.java @@ -16,19 +16,24 @@ package com.microsoft.windowsazure.services.queue.client; import java.io.OutputStream; +import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; import com.microsoft.windowsazure.services.core.storage.StorageException; import com.microsoft.windowsazure.services.core.storage.StorageExtendedErrorInformation; import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; import com.microsoft.windowsazure.services.core.storage.utils.Utility; import com.microsoft.windowsazure.services.core.storage.utils.implementation.BaseResponse; import com.microsoft.windowsazure.services.core.storage.utils.implementation.ExecutionEngine; @@ -70,7 +75,7 @@ static CloudQueueMessage getFirstOrNull(final Iterable<CloudQueueMessage> messag /** * A reference to the queue's associated service client. */ - private CloudQueueClient queueServiceClient; + CloudQueueClient queueServiceClient; /** * The queue's Metadata collection. @@ -103,8 +108,10 @@ static CloudQueueMessage getFirstOrNull(final Iterable<CloudQueueMessage> messag * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - public CloudQueue(final String queueAddress, final CloudQueueClient client) throws URISyntaxException { + public CloudQueue(final String queueAddress, final CloudQueueClient client) throws URISyntaxException, + StorageException { this(PathUtility.appendPathToUri(client.getEndpoint(), queueAddress), client); } @@ -116,12 +123,15 @@ public CloudQueue(final String queueAddress, final CloudQueueClient client) thro * @param client * A {@link CloudQueueClient} object that represents the associated service client, and that specifies * the endpoint for the Queue service. + * @throws StorageException + * @throws URISyntaxException */ - public CloudQueue(final URI uri, final CloudQueueClient client) { + public CloudQueue(final URI uri, final CloudQueueClient client) throws URISyntaxException, StorageException { this.uri = uri; this.name = PathUtility.getQueueNameFromUri(uri, client.isUsePathStyleUris()); this.queueServiceClient = client; this.shouldEncodeMessage = true; + this.parseQueryAndVerify(this.uri, client, client.isUsePathStyleUris()); } /** @@ -200,8 +210,8 @@ public void addMessage(final CloudQueueMessage message, final int timeToLiveInSe public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.putMessage(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), initialVisibilityDelayInSeconds, + final HttpURLConnection request = QueueRequest.putMessage(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), initialVisibilityDelayInSeconds, timeToLiveInSeconds, opContext); final byte[] messageBytes = QueueRequest.generateMessageRequestBody(stringToSend); @@ -271,8 +281,8 @@ public void clear(QueueRequestOptions options, OperationContext opContext) throw public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.clearMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.clearMessages(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -335,8 +345,8 @@ public void create(QueueRequestOptions options, OperationContext opContext) thro @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.create(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.create(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -408,8 +418,8 @@ public boolean createIfNotExist(QueueRequestOptions options, OperationContext op @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.create(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.create(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -494,8 +504,8 @@ public void delete(QueueRequestOptions options, OperationContext opContext) thro @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.delete(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.delete(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -565,8 +575,8 @@ public boolean deleteIfExists(QueueRequestOptions options, OperationContext opCo @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.delete(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.delete(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); client.getCredentials().signRequest(request, -1L); @@ -648,9 +658,9 @@ public void deleteMessage(final CloudQueueMessage message, QueueRequestOptions o public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.deleteMessage( - queue.getIndividualMessageAddress(messageId), - this.getRequestOptions().getTimeoutIntervalInMs(), messagePopReceipt, opContext); + final HttpURLConnection request = QueueRequest.deleteMessage(queue.getIndividualMessageAddress( + messageId, opContext), this.getRequestOptions().getTimeoutIntervalInMs(), messagePopReceipt, + opContext); client.getCredentials().signRequest(request, -1L); @@ -714,8 +724,9 @@ public void downloadAttributes(QueueRequestOptions options, OperationContext opC @Override public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.downloadAttributes(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.downloadAttributes( + queue.getTransformedAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + opContext); client.getCredentials().signRequest(request, -1L); @@ -786,8 +797,9 @@ public boolean exists(QueueRequestOptions options, OperationContext opContext) t @Override public Boolean execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.downloadAttributes(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.downloadAttributes( + queue.getTransformedAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + opContext); client.getCredentials().signRequest(request, -1L); @@ -828,9 +840,11 @@ public long getApproximateMessageCount() { * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - URI getIndividualMessageAddress(final String messageId) throws URISyntaxException { - return PathUtility.appendPathToUri(this.messageRequestAddress, messageId); + URI getIndividualMessageAddress(final String messageId, final OperationContext opContext) + throws URISyntaxException, StorageException { + return PathUtility.appendPathToUri(this.getMessageRequestAddress(opContext), messageId); } /** @@ -840,10 +854,12 @@ URI getIndividualMessageAddress(final String messageId) throws URISyntaxExceptio * * @throws URISyntaxException * If the resource URI is invalid. + * @throws StorageException */ - URI getMessageRequestAddress() throws URISyntaxException { + URI getMessageRequestAddress(final OperationContext opContext) throws URISyntaxException, StorageException { if (this.messageRequestAddress == null) { - this.messageRequestAddress = PathUtility.appendPathToUri(this.uri, QueueConstants.MESSAGES); + this.messageRequestAddress = PathUtility.appendPathToUri(this.getTransformedAddress(opContext), + QueueConstants.MESSAGES); } return this.messageRequestAddress; @@ -997,8 +1013,8 @@ public Iterable<CloudQueueMessage> peekMessages(final int numberOfMessages, Queu public ArrayList<CloudQueueMessage> execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.peekMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, opContext); + final HttpURLConnection request = QueueRequest.peekMessages(queue.getMessageRequestAddress(opContext), + this.getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, opContext); client.getCredentials().signRequest(request, -1L); @@ -1130,9 +1146,9 @@ public Iterable<CloudQueueMessage> retrieveMessages(final int numberOfMessages, public ArrayList<CloudQueueMessage> execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.retrieveMessages(queue.getMessageRequestAddress(), this - .getRequestOptions().getTimeoutIntervalInMs(), numberOfMessages, visibilityTimeoutInSeconds, - opContext); + final HttpURLConnection request = QueueRequest.retrieveMessages( + queue.getMessageRequestAddress(opContext), this.getRequestOptions().getTimeoutIntervalInMs(), + numberOfMessages, visibilityTimeoutInSeconds, opContext); client.getCredentials().signRequest(request, -1L); @@ -1258,9 +1274,9 @@ public void updateMessage(final CloudQueueMessage message, final int visibilityT public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.updateMessage(queue.getIndividualMessageAddress(message - .getId()), this.getRequestOptions().getTimeoutIntervalInMs(), message.getPopReceipt(), - visibilityTimeoutInSeconds, opContext); + final HttpURLConnection request = QueueRequest.updateMessage(queue.getIndividualMessageAddress( + message.getId(), opContext), this.getRequestOptions().getTimeoutIntervalInMs(), message + .getPopReceipt(), visibilityTimeoutInSeconds, opContext); if (messageUpdateFields.contains(MessageUpdateFields.CONTENT)) { final byte[] messageBytes = QueueRequest.generateMessageRequestBody(stringToSend); @@ -1341,8 +1357,8 @@ public void uploadMetadata(QueueRequestOptions options, OperationContext opConte public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) throws Exception { - final HttpURLConnection request = QueueRequest.setMetadata(queue.uri, this.getRequestOptions() - .getTimeoutIntervalInMs(), opContext); + final HttpURLConnection request = QueueRequest.setMetadata(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); QueueRequest.addMetadata(request, queue.metadata, opContext); client.getCredentials().signRequest(request, 0L); @@ -1361,4 +1377,290 @@ public Void execute(final CloudQueueClient client, final CloudQueue queue, final opContext); } + + /** + * Uploads the queue's permissions. + * + * @param permissions + * A {@link QueuePermissions} object that represents the permissions to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final QueuePermissions permissions) throws StorageException { + this.uploadPermissions(permissions, null, null); + } + + /** + * Uploads the queue's permissions using the specified request options and operation context. + * + * @param permissions + * A {@link QueuePermissions} object that represents the permissions to upload. + * @param options + * A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudQueueClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final QueuePermissions permissions, QueueRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new QueueRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.queueServiceClient); + + final StorageOperation<CloudQueueClient, CloudQueue, Void> impl = new StorageOperation<CloudQueueClient, CloudQueue, Void>( + options) { + + @Override + public Void execute(final CloudQueueClient client, final CloudQueue queue, final OperationContext opContext) + throws Exception { + + final HttpURLConnection request = QueueRequest.setAcl(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); + + final StringWriter outBuffer = new StringWriter(); + + QueueRequest.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(), outBuffer); + + final byte[] aclBytes = outBuffer.toString().getBytes("UTF8"); + client.getCredentials().signRequest(request, aclBytes.length); + final OutputStream outStreamRef = request.getOutputStream(); + outStreamRef.write(aclBytes); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + this.setNonExceptionedRetryableFailure(true); + } + + return null; + } + }; + + ExecutionEngine.executeWithRetry(this.queueServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Downloads the permission settings for the queue. + * + * @return A {@link QueuePermissions} object that represents the queue's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public QueuePermissions downloadPermissions() throws StorageException { + return this.downloadPermissions(null, null); + } + + /** + * Downloads the permissions settings for the queue using the specified request options and operation context. + * + * @param options + * A {@link QueueRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudQueueClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link QueuePermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public QueuePermissions downloadPermissions(QueueRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new QueueRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.queueServiceClient); + + final StorageOperation<CloudQueueClient, CloudQueue, QueuePermissions> impl = new StorageOperation<CloudQueueClient, CloudQueue, QueuePermissions>( + options) { + + @Override + public QueuePermissions execute(final CloudQueueClient client, final CloudQueue queue, + final OperationContext opContext) throws Exception { + + final HttpURLConnection request = QueueRequest.getAcl(queue.getTransformedAddress(opContext), this + .getRequestOptions().getTimeoutIntervalInMs(), opContext); + + client.getCredentials().signRequest(request, -1L); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + final QueuePermissions permissions = new QueuePermissions(); + final QueueAccessPolicyResponse response = new QueueAccessPolicyResponse(request.getInputStream()); + + for (final String key : response.getAccessIdentifiers().keySet()) { + permissions.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); + } + + return permissions; + } + }; + + return ExecutionEngine.executeWithRetry(this.queueServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Returns a shared access signature for the queue. + * + * @param policy + * The access policy for the shared access signature. + * @param groupPolicyIdentifier + * A queue-level access policy. + * @return a shared access signature for the container. + * @throws InvalidKeyException + * @throws StorageException + * @throws IllegalArgumentException + */ + public String generateSharedAccessSignature(final SharedAccessQueuePolicy policy, final String groupPolicyIdentifier) + throws InvalidKeyException, StorageException { + + if (!this.queueServiceClient.getCredentials().canCredentialsSignRequest()) { + final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the QueueServiceClient."; + throw new IllegalArgumentException(errorMessage); + } + + final String resourceName = this.getSharedAccessCanonicalName(); + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, + groupPolicyIdentifier, resourceName, this.queueServiceClient, null); + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, + groupPolicyIdentifier, signature); + + return builder.toString(); + } + + /** + * Returns the canonical name for shared access. + * + * @return the canonical name for shared access. + */ + private String getSharedAccessCanonicalName() { + if (this.queueServiceClient.isUsePathStyleUris()) { + return this.getUri().getPath(); + } + else { + return PathUtility.getCanonicalPathFromCredentials(this.queueServiceClient.getCredentials(), this.getUri() + .getPath()); + } + } + + /** + * Returns the transformed URI for the resource if the given credentials require transformation. + * + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A <code>java.net.URI</code> object that represents the transformed URI. + * + * @throws IllegalArgumentException + * If the URI is not absolute. + * @throws StorageException + * If a storage service error occurred. + * @throws URISyntaxException + * If the resource URI is invalid. + */ + protected final URI getTransformedAddress(final OperationContext opContext) throws URISyntaxException, + StorageException { + if (this.queueServiceClient.getCredentials().doCredentialsNeedTransformUri()) { + if (this.getUri().isAbsolute()) { + return this.queueServiceClient.getCredentials().transformUri(this.getUri(), opContext); + } + else { + final StorageException ex = Utility.generateNewUnexpectedStorageException(null); + ex.getExtendedErrorInformation().setErrorMessage("Queue Object relative URIs not supported."); + throw ex; + } + } + else { + return this.getUri(); + } + } + + /** + * Parse Uri for SAS (Shared access signature) information. + * + * Validate that no other query parameters are passed in. Any SAS information will be recorded as corresponding + * credentials instance. If existingClient is passed in, any SAS information found will not be supported. Otherwise + * a new client is created based on SAS information or as anonymous credentials. + * + * @param completeUri + * The complete Uri. + * @param existingClient + * The client to use. + * @param usePathStyleUris + * If true, path style Uris are used. + * @throws URISyntaxException + * @throws StorageException + */ + private void parseQueryAndVerify(final URI completeUri, final CloudQueueClient existingClient, + final boolean usePathStyleUris) throws URISyntaxException, StorageException { + Utility.assertNotNull("completeUri", completeUri); + + if (!completeUri.isAbsolute()) { + final String errorMessage = String.format( + "Address '%s' is not an absolute address. Relative addresses are not permitted in here.", + completeUri.toString()); + throw new IllegalArgumentException(errorMessage); + } + + this.uri = PathUtility.stripURIQueryAndFragment(completeUri); + + final HashMap<String, String[]> queryParameters = PathUtility.parseQueryString(completeUri.getQuery()); + final StorageCredentialsSharedAccessSignature sasCreds = SharedAccessSignatureHelper + .parseQuery(queryParameters); + + if (sasCreds == null) { + return; + } + + final Boolean sameCredentials = existingClient == null ? false : Utility.areCredentialsEqual(sasCreds, + existingClient.getCredentials()); + + if (existingClient == null || !sameCredentials) { + this.queueServiceClient = new CloudQueueClient(new URI(PathUtility.getServiceClientBaseAddress( + this.getUri(), usePathStyleUris)), sasCreds); + } + + if (existingClient != null && !sameCredentials) { + this.queueServiceClient.setRetryPolicyFactory(existingClient.getRetryPolicyFactory()); + this.queueServiceClient.setTimeoutInMs(existingClient.getTimeoutInMs()); + } + } } diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java new file mode 100644 index 0000000000000..457113684d9d1 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.queue.client; + +import java.io.InputStream; +import java.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class QueueAccessPolicyResponse extends AccessPolicyResponseBase<SharedAccessQueuePolicy> { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public QueueAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessQueuePolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessQueuePolicy retPolicy = new SharedAccessQueuePolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessQueuePolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java new file mode 100644 index 0000000000000..6454f1b86fe8f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueuePermissions.java @@ -0,0 +1,57 @@ +/** + * Copyright 2011 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.queue.client; + +import java.util.HashMap; + +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePolicy; + +/** + * Represents the permissions for a container. + */ + +public final class QueuePermissions { + + /** + * Gets the set of shared access policies for the table. + */ + private HashMap<String, SharedAccessQueuePolicy> sharedAccessPolicies; + + /** + * Creates an instance of the <code>TablePermissions</code> class. + */ + public QueuePermissions() { + this.sharedAccessPolicies = new HashMap<String, SharedAccessQueuePolicy>(); + } + + /** + * Returns the set of shared access policies for the table. + * + * @return A <code>HashMap</code> object of {@link SharedAccessTablePolicy} objects that represent the set of shared + * access policies for the table. + */ + public HashMap<String, SharedAccessQueuePolicy> getSharedAccessPolicies() { + return this.sharedAccessPolicies; + } + + /** + * @param sharedAccessPolicies + * the sharedAccessPolicies to set + */ + public void setSharedAccessPolicies(final HashMap<String, SharedAccessQueuePolicy> sharedAccessPolicies) { + this.sharedAccessPolicies = sharedAccessPolicies; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java index 1b8867dc1797c..cf0c6316a0c60 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/QueueRequest.java @@ -22,6 +22,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.Map.Entry; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; @@ -612,6 +613,124 @@ public static HttpURLConnection updateMessage(final URI uri, final int timeout, return request; } + /** + * Sets the ACL for the queue. , Sign with length of aclBytes. + * + * @param uri + * The absolute URI to the queue. + * @param timeout + * The server timeout interval. + * @param publicAccess + * The type of public access to allow for the queue. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * */ + public static HttpURLConnection setAcl(final URI uri, final int timeout, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add("comp", "acl"); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setDoOutput(true); + request.setRequestMethod("PUT"); + + return request; + } + + /** + * Writes a collection of shared access policies to the specified stream in XML format. + * + * @param sharedAccessPolicies + * A collection of shared access policies + * @param outWriter + * an sink to write the output to. + * @throws XMLStreamException + */ + public static void writeSharedAccessIdentifiersToStream( + final HashMap<String, SharedAccessQueuePolicy> sharedAccessPolicies, final StringWriter outWriter) + throws XMLStreamException { + Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); + Utility.assertNotNull("outWriter", outWriter); + + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); + + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + final String errorMessage = String + .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single queue.", + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + + throw new IllegalArgumentException(errorMessage); + } + + // default is UTF8 + xmlw.writeStartDocument(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); + + for (final Entry<String, SharedAccessQueuePolicy> entry : sharedAccessPolicies.entrySet()) { + final SharedAccessQueuePolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); + + // Set the identifier + xmlw.writeStartElement(Constants.ID); + xmlw.writeCharacters(entry.getKey()); + xmlw.writeEndElement(); + + xmlw.writeStartElement(Constants.ACCESS_POLICY); + + // Set the Start Time + xmlw.writeStartElement(Constants.START); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); + // end Start + xmlw.writeEndElement(); + + // Set the Expiry Time + xmlw.writeStartElement(Constants.EXPIRY); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); + // end Expiry + xmlw.writeEndElement(); + + // Set the Permissions + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessQueuePolicy.permissionsToString(policy.getPermissions())); + // end Permission + xmlw.writeEndElement(); + + // end AccessPolicy + xmlw.writeEndElement(); + // end SignedIdentifier + xmlw.writeEndElement(); + } + + // end SignedIdentifiers + xmlw.writeEndElement(); + // end doc + xmlw.writeEndDocument(); + } + + /** + * Constructs a web request to return the ACL for this queue. Sign with no length specified. + * + * @param uri + * The absolute URI to the container. + * @param timeout + * The server timeout interval. + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getAcl(final URI uri, final int timeout, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + final UriQueryBuilder builder = new UriQueryBuilder(); + builder.add("comp", "acl"); + + final HttpURLConnection request = BaseRequest.createURLConnection(uri, timeout, builder, opContext); + + request.setRequestMethod("GET"); + + return request; + } + /** * Private Default Ctor. */ diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java new file mode 100644 index 0000000000000..bec37d8d33d12 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePermissions.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 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.queue.client; + +import java.util.EnumSet; + +/** + * Specifies the set of possible permissions for a shared access queue policy. + */ +public enum SharedAccessQueuePermissions { + + /** + * No shared access granted. + */ + NONE((byte) 0x0), + + /** + * Permission to peek messages and get queue metadata granted. + */ + READ((byte) 0x1), + + /** + * Permission to add messages granted. + */ + ADD((byte) 0x2), + + /** + * Permissions to update messages granted. + */ + UPDATE((byte) 0x4), + + /** + * Permission to get and delete messages granted. + */ + PROCESSMESSAGES((byte) 0x8); + + /** + * Returns the enum set representing the shared access permissions for the specified byte value. + * + * @param value + * The byte value to convert to the corresponding enum set. + * @return A <code>java.util.EnumSet</code> object that contains the <code>SharedAccessPermissions</code> values + * corresponding to the specified byte value. + */ + protected static EnumSet<SharedAccessQueuePermissions> fromByte(final byte value) { + final EnumSet<SharedAccessQueuePermissions> retSet = EnumSet.noneOf(SharedAccessQueuePermissions.class); + + if (value == READ.value) { + retSet.add(READ); + } + + if (value == PROCESSMESSAGES.value) { + retSet.add(PROCESSMESSAGES); + } + if (value == ADD.value) { + retSet.add(ADD); + } + if (value == UPDATE.value) { + retSet.add(UPDATE); + } + + return retSet; + } + + /** + * Returns the value of this enum. + */ + private byte value; + + /** + * Sets the value of this enum. + * + * @param val + * The value being assigned. + */ + SharedAccessQueuePermissions(final byte val) { + this.value = val; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java new file mode 100644 index 0000000000000..54bdf677ad44f --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/client/SharedAccessQueuePolicy.java @@ -0,0 +1,174 @@ +/** + * Copyright 2011 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.queue.client; + +import java.util.Date; +import java.util.EnumSet; + +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.table.client.SharedAccessTablePermissions; + +/** + * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access + * signature. + */ +public final class SharedAccessQueuePolicy { + + /** + * Assigns shared access permissions using the specified permissions string. + * + * @param value + * A <code>String</code> that represents the shared access permissions. The string must contain one or + * more of the following values. Note they must be lowercase, and the order that they are specified must + * be in the order of "rwdl". + * <ul> + * <li><code>d</code>: Delete access.</li> + * <li><code>l</code>: List access.</li> + * <li><code>r</code>: Read access.</li> + * <li><code>w</code>: Write access.</li> + * </ul> + * + * @return A <code>java.util.EnumSet</code> object that contains {@link SharedAccessTablePermissions} values that + * represents the set of shared access permissions. + */ + public static EnumSet<SharedAccessQueuePermissions> permissionsFromString(final String value) { + final char[] chars = value.toCharArray(); + final EnumSet<SharedAccessQueuePermissions> retSet = EnumSet.noneOf(SharedAccessQueuePermissions.class); + + for (final char c : chars) { + switch (c) { + case 'r': + retSet.add(SharedAccessQueuePermissions.READ); + break; + case 'a': + retSet.add(SharedAccessQueuePermissions.ADD); + break; + case 'u': + retSet.add(SharedAccessQueuePermissions.UPDATE); + break; + case 'p': + retSet.add(SharedAccessQueuePermissions.PROCESSMESSAGES); + break; + default: + throw new IllegalArgumentException("value"); + } + } + + return retSet; + } + + /** + * Converts the permissions specified for the shared access policy to a string. + * + * @param permissions + * A {@link SharedAccessQueuePermissions} object that represents the shared access permissions. + * + * @return A <code>String</code> that represents the shared access permissions in the "rwdl" format, which is + * described at {@link SharedAccessQueuePermissions#permissionsFromString}. + */ + public static String permissionsToString(final EnumSet<SharedAccessQueuePermissions> permissions) { + if (permissions == null) { + return Constants.EMPTY_STRING; + } + + // The service supports a fixed order => rwdl + final StringBuilder builder = new StringBuilder(); + + if (permissions.contains(SharedAccessQueuePermissions.READ)) { + builder.append("r"); + } + + if (permissions.contains(SharedAccessQueuePermissions.ADD)) { + builder.append("a"); + } + + if (permissions.contains(SharedAccessQueuePermissions.UPDATE)) { + builder.append("u"); + } + + if (permissions.contains(SharedAccessQueuePermissions.PROCESSMESSAGES)) { + builder.append("p"); + } + + return builder.toString(); + } + + /** + * The permissions for a shared access signature associated with this shared access policy. + */ + private EnumSet<SharedAccessQueuePermissions> permissions; + + /** + * The expiry time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessExpiryTime; + + /** + * The start time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessStartTime; + + /** + * Creates an instance of the <code>SharedAccessTablePolicy</code> class. + * */ + public SharedAccessQueuePolicy() { + // Empty Default Ctor + } + + /** + * @return the permissions + */ + public EnumSet<SharedAccessQueuePermissions> getPermissions() { + return this.permissions; + } + + /** + * @return the sharedAccessExpiryTime + */ + public Date getSharedAccessExpiryTime() { + return this.sharedAccessExpiryTime; + } + + /** + * @return the sharedAccessStartTime + */ + public Date getSharedAccessStartTime() { + return this.sharedAccessStartTime; + } + + /** + * @param permissions + * the permissions to set + */ + public void setPermissions(final EnumSet<SharedAccessQueuePermissions> permissions) { + this.permissions = permissions; + } + + /** + * @param sharedAccessExpiryTime + * the sharedAccessExpiryTime to set + */ + public void setSharedAccessExpiryTime(final Date sharedAccessExpiryTime) { + this.sharedAccessExpiryTime = sharedAccessExpiryTime; + } + + /** + * @param sharedAccessStartTime + * the sharedAccessStartTime to set + */ + public void setSharedAccessStartTime(final Date sharedAccessStartTime) { + this.sharedAccessStartTime = sharedAccessStartTime; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java new file mode 100644 index 0000000000000..2ec64eceaf53a --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTable.java @@ -0,0 +1,643 @@ +/** + * Copyright 2011 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.OutputStream; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; + +import com.microsoft.windowsazure.services.blob.client.BlobContainerPermissions; +import com.microsoft.windowsazure.services.blob.core.storage.SharedAccessSignatureHelper; +import com.microsoft.windowsazure.services.core.storage.DoesServiceRequest; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentials; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsAccountAndKey; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; +import com.microsoft.windowsazure.services.core.storage.utils.PathUtility; +import com.microsoft.windowsazure.services.core.storage.utils.UriQueryBuilder; +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; + +/** + * Represents a table in the Windows Azure Table service. + */ +public final class CloudTable { + + /** + * Holds the name of the table. + */ + String name; + + /** + * Holds the URI of the table. + */ + URI uri; + + /** + * Holds a reference to the associated service client. + */ + private final CloudTableClient tableServiceClient; + + /** + * Gets the name of the queue. + * + * @return A <code>String</code> object that represents the name of the queue. + */ + public String getName() { + return this.name; + } + + /** + * Gets the table service client associated with this queue. + * + * @return A {@link CloudTableClient} object that represents the service client associated with this table. + */ + public CloudTableClient getServiceClient() { + return this.tableServiceClient; + } + + /** + * Gets the absolute URI for this queue. + * + * @return A <code>java.net.URI</code> object that represents the URI for this queue. + */ + public URI getUri() { + return this.uri; + } + + /** + * Creates an instance of the <code>CloudTable</code> class using the specified address and client. + * + * @param tableAddress + * A <code>String</code> that represents the table name. + * @param client + * A {@link CloudTableClient} object that represents the associated service client, and that specifies + * the endpoint for the Table service. + * + * @throws URISyntaxException + * If the resource URI is invalid. + */ + public CloudTable(final String tableName, final CloudTableClient client) throws URISyntaxException { + this(PathUtility.appendPathToUri(client.getEndpoint(), tableName), client); + } + + /** + * Creates an instance of the <code>CloudTable</code> class using the specified table URI and client. + * + * @param uri + * A <code>java.net.URI</code> object that represents the absolute URI of the table. + * @param client + * A {@link CloudTableClient} object that represents the associated service client, and that specifies + * the endpoint for the Table service. + */ + public CloudTable(final URI uri, final CloudTableClient client) { + this.uri = uri; + this.name = PathUtility.getTableNameFromUri(uri, client.isUsePathStyleUris()); + this.tableServiceClient = client; + } + + /** + * Creates the table in the storage service with default request options. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create + * Table</a> REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public void create() throws StorageException { + this.create(null, null); + } + + /** + * Creates the table in the storage service, using the specified {@link TableRequestOptions} and + * {@link OperationContext}. + * <p> + * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create + * Table</a> REST API to create the specified table, using the Table service endpoint and storage account + * credentials of this instance. + * + * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the + * operation. + * + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @throws StorageException + * if an error occurs accessing the storage service, or because the table cannot be + * created, or already exists. + */ + @DoesServiceRequest + public void create(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name)); + + this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, TableOperation.insert(tableEntry), + options, opContext); + } + + /** + * Creates the table in the storage service using default request options if it does not already exist. + * + * @return A value of <code>true</code> if the table is created in the storage service, otherwise <code>false</code> + * . + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean createIfNotExist() throws StorageException { + return this.createIfNotExist(null, null); + } + + /** + * Creates the table in the storage service with the specified request options and operation context if it does not + * already exist. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A value of <code>true</code> if the table is created in the storage service, otherwise <code>false</code> + * . + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean createIfNotExist(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + if (this.exists(options, opContext)) { + return false; + } + else { + try { + this.create(options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT + && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + } + + /** + * Deletes the table from the storage service. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public void delete() throws StorageException { + this.delete(null, null); + } + + /** + * Deletes the table from the storage service, using the specified request options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public void delete(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + final DynamicTableEntity tableEntry = new DynamicTableEntity(); + tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(this.name)); + + final TableOperation delOp = new TableOperation(tableEntry, TableOperationType.DELETE); + + final TableResult result = this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, delOp, + options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { + return; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + + } + + /** + * Deletes the table from the storage service if it exists. + * + * @return A value of <code>true</code> if the table existed in the storage service and has been deleted, otherwise + * <code>false</code>. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean deleteIfExists() throws StorageException { + return this.deleteIfExists(null, null); + } + + /** + * Deletes the table from the storage service using the specified request options and operation context, if it + * exists. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A value of <code>true</code> if the table existed in the storage service and has been deleted, otherwise + * <code>false</code>. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean deleteIfExists(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + if (this.exists(options, opContext)) { + try { + this.delete(options, opContext); + } + catch (StorageException ex) { + if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND + && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) { + return false; + } + else { + throw ex; + } + } + return true; + } + else { + return false; + } + + } + + /** + * Returns a value that indicates whether the table exists in the storage service. + * + * @return <code>true</code> if the table exists in the storage service, otherwise <code>false</code>. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean exists() throws StorageException { + return this.exists(null, null); + } + + /** + * Returns a value that indicates whether the table exists in the storage service, using the specified request + * options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return <code>true</code> if the table exists in the storage service, otherwise <code>false</code>. + * + * @throws StorageException + * If a storage service error occurred during the operation. + */ + @DoesServiceRequest + public boolean exists(TableRequestOptions options, OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + Utility.assertNotNullOrEmpty("tableName", this.name); + + final TableResult result = this.tableServiceClient.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, + TableOperation.retrieve(this.name /* Used As PK */, null/* Row Key */, DynamicTableEntity.class), + options, opContext); + + if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) { + return true; + } + else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { + return false; + } + else { + throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, + "Unexpected http status code received.", result.getHttpStatusCode(), null, null); + } + } + + /** + * Uploads the table's permissions. + * + * @param permissions + * A {@link TablePermissions} object that represents the permissions to upload. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final TablePermissions permissions) throws StorageException { + this.uploadPermissions(permissions, null, null); + } + + /** + * Uploads the container's permissions using the specified request options and operation context. + * + * @param permissions + * A {@link TablePermissions} object that represents the permissions to upload. + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public void uploadPermissions(final TablePermissions permissions, TableRequestOptions options, + OperationContext opContext) throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + + final StorageOperation<CloudTableClient, CloudTable, Void> impl = new StorageOperation<CloudTableClient, CloudTable, Void>( + options) { + + @Override + public Void execute(final CloudTableClient client, final CloudTable table, final OperationContext opContext) + throws Exception { + + final HttpURLConnection request = TableRequest.setAcl(table.uri, this.getRequestOptions() + .getTimeoutIntervalInMs(), opContext); + + final StringWriter outBuffer = new StringWriter(); + + TableRequest.writeSharedAccessIdentifiersToStream(permissions.getSharedAccessPolicies(), outBuffer); + + final byte[] aclBytes = outBuffer.toString().getBytes("UTF8"); + client.getCredentials().signRequestLite(request, aclBytes.length, opContext); + final OutputStream outStreamRef = request.getOutputStream(); + outStreamRef.write(aclBytes); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + this.setNonExceptionedRetryableFailure(true); + } + + return null; + } + }; + + ExecutionEngine.executeWithRetry(this.tableServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Downloads the permission settings for the table. + * + * @return A {@link TablePermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public TablePermissions downloadPermissions() throws StorageException { + return this.downloadPermissions(null, null); + } + + /** + * Downloads the permissions settings for the table using the specified request options and operation context. + * + * @param options + * A {@link TableRequestOptions} object that specifies any additional options for the request. Specifying + * <code>null</code> will use the default request options from the associated service client ( + * {@link CloudTableClient}). + * @param opContext + * An {@link OperationContext} object that represents the context for the current operation. This object + * is used to track requests to the storage service, and to provide additional runtime information about + * the operation. + * + * @return A {@link BlobContainerPermissions} object that represents the container's permissions. + * + * @throws StorageException + * If a storage service error occurred. + */ + @DoesServiceRequest + public TablePermissions downloadPermissions(TableRequestOptions options, OperationContext opContext) + throws StorageException { + if (opContext == null) { + opContext = new OperationContext(); + } + + if (options == null) { + options = new TableRequestOptions(); + } + + opContext.initialize(); + options.applyDefaults(this.tableServiceClient); + final String tableName = this.name; + + final StorageOperation<CloudTableClient, CloudTable, TablePermissions> impl = new StorageOperation<CloudTableClient, CloudTable, TablePermissions>( + options) { + + @Override + public TablePermissions execute(final CloudTableClient client, final CloudTable table, + final OperationContext opContext) throws Exception { + + final HttpURLConnection request = TableRequest.getAcl(table.uri, tableName, this.getRequestOptions() + .getTimeoutIntervalInMs(), opContext); + + client.getCredentials().signRequestLite(request, -1L, opContext); + + this.setResult(ExecutionEngine.processRequest(request, opContext)); + + if (this.getResult().getStatusCode() != HttpURLConnection.HTTP_OK) { + this.setNonExceptionedRetryableFailure(true); + } + + final TablePermissions permissions = new TablePermissions(); + final TableAccessPolicyResponse response = new TableAccessPolicyResponse(request.getInputStream()); + for (final String key : response.getAccessIdentifiers().keySet()) { + permissions.getSharedAccessPolicies().put(key, response.getAccessIdentifiers().get(key)); + } + + return permissions; + } + }; + + return ExecutionEngine.executeWithRetry(this.tableServiceClient, this, impl, options.getRetryPolicyFactory(), + opContext); + } + + /** + * Returns a shared access signature for the table. + * + * @param policy + * The access policy for the shared access signature. + * @param accessPolicyIdentifier + * A table-level access policy. + * @return a shared access signature for the container. + * @throws InvalidKeyException + * @throws StorageException + * @throws IllegalArgumentException + */ + public String generateSharedAccessSignature(final SharedAccessTablePolicy policy, + final String accessPolicyIdentifier, final String startPartitionKey, final String startRowKey, + final String endPartitionKey, final String endRowKey) throws InvalidKeyException, StorageException { + + if (!this.tableServiceClient.getCredentials().canCredentialsSignRequest()) { + final String errorMessage = "Cannot create Shared Access Signature unless the Account Key credentials are used by the BlobServiceClient."; + throw new IllegalArgumentException(errorMessage); + } + + final String resourceName = this.getSharedAccessCanonicalName(); + + final String signature = SharedAccessSignatureHelper.generateSharedAccessSignatureHash(policy, + accessPolicyIdentifier, resourceName, startPartitionKey, startRowKey, endPartitionKey, endRowKey, + this.tableServiceClient, null); + + String accountKeyName = null; + StorageCredentials credentials = this.tableServiceClient.getCredentials(); + + if (credentials instanceof StorageCredentialsAccountAndKey) { + accountKeyName = ((StorageCredentialsAccountAndKey) credentials).getAccountKeyName(); + } + + final UriQueryBuilder builder = SharedAccessSignatureHelper.generateSharedAccessSignature(policy, + startPartitionKey, startRowKey, endPartitionKey, endRowKey, accessPolicyIdentifier, this.name, + signature, accountKeyName); + + return builder.toString(); + } + + /** + * Returns the canonical name for shared access. + * + * @return the canonical name for shared access. + */ + private String getSharedAccessCanonicalName() { + if (this.tableServiceClient.isUsePathStyleUris()) { + return this.getUri().getPath(); + } + else { + return PathUtility.getCanonicalPathFromCredentials(this.tableServiceClient.getCredentials(), this.getUri() + .getPath()); + } + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java index 00075de491922..4420c2d375180 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/CloudTableClient.java @@ -35,13 +35,13 @@ import com.microsoft.windowsazure.services.core.storage.ResultSegment; import com.microsoft.windowsazure.services.core.storage.ServiceClient; import com.microsoft.windowsazure.services.core.storage.StorageCredentials; -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.LazySegmentedIterable; import com.microsoft.windowsazure.services.core.storage.utils.implementation.SegmentedStorageOperation; import com.microsoft.windowsazure.services.core.storage.utils.implementation.StorageOperation; +import com.microsoft.windowsazure.services.queue.client.CloudQueue; /** * Provides a service client for accessing the Windows Azure Table service. @@ -108,400 +108,24 @@ public CloudTableClient(final URI baseUri, StorageCredentials credentials) { } /** - * Creates a table with the specified name in the storage service. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create - * Table</a> REST API to create the specified table, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A <code>String</code> object containing the name of the table to create. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table cannot be - * created, or already exists. - */ - @DoesServiceRequest - public void createTable(final String tableName) throws StorageException { - this.createTable(tableName, null, null); - } - - /** - * Creates a table with the specified name in the storage service, using the specified {@link TableRequestOptions} - * and {@link OperationContext}. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create - * Table</a> REST API to create the specified table, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A <code>String</code> object containing the name of the table to create. - * @param options - * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify <code>null</code> to use the request options specified on the - * {@link CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to - * safely ignore operation context. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table cannot be - * created, or already exists. - */ - @DoesServiceRequest - public void createTable(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(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - final DynamicTableEntity tableEntry = new DynamicTableEntity(); - tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); - - this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, TableOperation.insert(tableEntry), options, opContext); - } - - /** - * Creates a table with the specified name in the storage service, if it does not already exist. - * <p> - * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, and if not, invokes the <a - * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create Table</a> Storage Service REST - * API to create the specified table, using the Table service endpoint and storage account credentials of this - * instance. - * - * @param tableName - * A <code>String</code> object containing the name of the table to create. - * - * @return - * A value of <code>true</code> if the operation created a new table, otherwise <code>false</code>. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table does not - * exist and cannot be created. - */ - @DoesServiceRequest - public boolean createTableIfNotExists(final String tableName) throws StorageException { - return this.createTableIfNotExists(tableName, null, null); - } - - /** - * Creates a table with the specified name in the storage service if it does not already exist, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - * <p> - * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, and if not, invokes the <a - * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd135729.aspx">Create Table</a> Storage Service REST - * API to create the specified table, using the Table service endpoint and storage account credentials of this - * instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A <code>String</code> object containing the name of the table to create. - * @param options - * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify <code>null</code> to use the request options specified on the - * {@link CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to - * safely ignore operation context. - * - * @return - * A value of <code>true</code> if the operation created a new table, otherwise <code>false</code>. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table does not - * exist and cannot be created. - */ - @DoesServiceRequest - public boolean createTableIfNotExists(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(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - if (this.doesTableExist(tableName, options, opContext)) { - return false; - } - else { - try { - this.createTable(tableName, options, opContext); - } - catch (StorageException ex) { - if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT - && StorageErrorCodeStrings.TABLE_ALREADY_EXISTS.equals(ex.getErrorCode())) { - return false; - } - else { - throw ex; - } - } - return true; - } - } - - /** - * Deletes the table with the specified name in the storage service. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete - * Table</a> REST API to delete the specified table and any data it contains, using the Table service endpoint and - * storage account credentials of this instance. + * Gets a {@link CloudTable} object that represents the storage service + * queue for the specified address. * - * @param tableName - * A <code>String</code> object containing the name of the table to delete. + * @param tableAddress + * A <code>String</code> that represents the name of the table, + * or the absolute URI to the queue. * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public void deleteTable(final String tableName) throws StorageException { - this.deleteTable(tableName, null, null); - } - - /** - * Deletes the table with the specified name in the storage service, using the specified {@link TableRequestOptions} - * and {@link OperationContext}. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete - * Table</a> REST API to delete the specified table and any data it contains, using the Table service endpoint and - * storage account credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A <code>String</code> object containing the name of the table to delete. - * @param options - * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify <code>null</code> to use the request options specified on the - * {@link CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to - * safely ignore operation context. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public void deleteTable(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(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - final DynamicTableEntity tableEntry = new DynamicTableEntity(); - tableEntry.getProperties().put(TableConstants.TABLE_NAME, new EntityProperty(tableName)); - - final TableOperation delOp = new TableOperation(tableEntry, TableOperationType.DELETE); - - final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, delOp, options, opContext); - - if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NO_CONTENT) { - return; - } - else { - throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, - "Unexpected http status code received.", result.getHttpStatusCode(), null, null); - } - } - - /** - * Deletes the table with the specified name in the storage service, if it exists. - * <p> - * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, and if so, invokes the <a - * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete Table</a> Storage Service REST - * API to delete the table and any data it contains, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A <code>String</code> object containing the name of the table to delete. - * - * @return - * A value of <code>true</code> if the operation deleted an existing table, otherwise <code>false</code>. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public boolean deleteTableIfExists(final String tableName) throws StorageException { - return this.deleteTableIfExists(tableName, null, null); - } - - /** - * Deletes the table with the specified name in the storage service, if it exists, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - * <p> - * This method first invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, and if so, invokes the <a - * href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179387.aspx">Delete Table</a> Storage Service REST - * API to delete the table and any data it contains, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A <code>String</code> object containing the name of the table to delete. - * @param options - * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify <code>null</code> to use the request options specified on the - * {@link CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to - * safely ignore operation context. - * - * @return - * A value of <code>true</code> if the operation deleted an existing table, otherwise <code>false</code>. - * - * @throws StorageException - * if an error occurs accessing the storage service, or because the table deletion operation failed. - */ - @DoesServiceRequest - public boolean deleteTableIfExists(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(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - if (this.doesTableExist(tableName, options, opContext)) { - try { - this.deleteTable(tableName, options, opContext); - } - catch (StorageException ex) { - if (ex.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND - && StorageErrorCodeStrings.RESOURCE_NOT_FOUND.equals(ex.getErrorCode())) { - return false; - } - else { - throw ex; - } - } - return true; - } - else { - return false; - } - } - - /** - * Determines if a table with the specified name exists in the storage service. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, using the Table service endpoint and storage account - * credentials of this instance. - * - * @param tableName - * A <code>String</code> object containing the name of the table to find. - * - * @return - * A value of <code>true</code> if the table exists, otherwise <code>false</code>. - * - * @throws StorageException - * if an error occurs accessing the storage service. - */ - @DoesServiceRequest - public boolean doesTableExist(final String tableName) throws StorageException { - return this.doesTableExist(tableName, null, null); - } - - /** - * Determines if a table with the specified name exists in the storage service, using the specified - * {@link TableRequestOptions} and {@link OperationContext}. - * <p> - * This method invokes the <a href="http://msdn.microsoft.com/en-us/library/windowsazure/dd179405.aspx">Query - * Tables</a> REST API to determine if the table exists, using the Table service endpoint and storage account - * credentials of this instance. - * - * Use the {@link TableRequestOptions} to override execution options such as the timeout or retry policy for the - * operation. - * - * @param tableName - * A <code>String</code> object containing the name of the table to find. - * @param options - * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout - * settings for the operation. Specify <code>null</code> to use the request options specified on the - * {@link CloudTableClient}. - * @param opContext - * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to - * safely ignore operation context. - * - * @return - * A value of <code>true</code> if the table exists, otherwise <code>false</code>. + * @return A {@link CloudQueue} object that represents a reference to the + * table. * + * @throws URISyntaxException + * If the resource URI is invalid. * @throws StorageException - * if an error occurs accessing the storage service. + * If a storage service error occurred during the operation. */ - @DoesServiceRequest - public boolean doesTableExist(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(this); - - Utility.assertNotNullOrEmpty("tableName", tableName); - - final TableResult result = this.execute(TableConstants.TABLES_SERVICE_TABLES_NAME, - TableOperation.retrieve(tableName /* Used As PK */, null/* Row Key */, DynamicTableEntity.class), - options, opContext); - - if (result.getHttpStatusCode() == HttpURLConnection.HTTP_OK) { - return true; - } - else if (result.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND) { - return false; - } - else { - throw new StorageException(StorageErrorCodeStrings.OUT_OF_RANGE_INPUT, - "Unexpected http status code received.", result.getHttpStatusCode(), null, null); - } + public CloudTable getTableReference(final String tableAddress) throws URISyntaxException, StorageException { + Utility.assertNotNullOrEmpty("tableAddress", tableAddress); + return new CloudTable(tableAddress, this); } /** @@ -1170,7 +794,7 @@ protected <T extends TableEntity, R> ResultSegment<?> executeQuerySegmentedCore( Utility.assertNotNull("Query requires a valid class type or resolver.", queryToExecute.getClazzType()); } - final HttpURLConnection queryRequest = TableRequest.query(this.getEndpoint(), + final HttpURLConnection queryRequest = TableRequest.query(this.getTransformedEndPoint(opContext), queryToExecute.getSourceTableName(), null/* identity */, options.getTimeoutIntervalInMs(), queryToExecute.generateQueryBuilder(), continuationToken, options, opContext); @@ -1270,6 +894,23 @@ public ResultSegment<?> execute(final CloudTableClient client, final TableQuery< return ExecutionEngine.executeWithRetry(this, queryToExecute, impl, options.getRetryPolicyFactory(), opContext); } + protected final URI getTransformedEndPoint(final OperationContext opContext) throws URISyntaxException, + StorageException { + if (this.getCredentials().doCredentialsNeedTransformUri()) { + if (this.getEndpoint().isAbsolute()) { + return this.getCredentials().transformUri(this.getEndpoint(), opContext); + } + else { + final StorageException ex = Utility.generateNewUnexpectedStorageException(null); + ex.getExtendedErrorInformation().setErrorMessage("Table Object relative URIs not supported."); + throw ex; + } + } + else { + return this.getEndpoint(); + } + } + /** * Reserved for internal use. Generates an iterator for a segmented query operation. * diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java index 2f02f4bb50e23..74098b013074e 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/QueryTableOperation.java @@ -182,8 +182,8 @@ protected TableResult performRetrieve(final CloudTableClient client, final Strin public TableResult execute(final CloudTableClient client, final QueryTableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.query(client.getEndpoint(), tableName, - generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), + final HttpURLConnection request = TableRequest.query(client.getTransformedEndPoint(opContext), + tableName, generateRequestIdentity(isTableEntry, operation.getPartitionKey(), false), options.getTimeoutIntervalInMs(), null/* Query Builder */, null/* Continuation Token */, options, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java new file mode 100644 index 0000000000000..3e75ac8e196c3 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePermissions.java @@ -0,0 +1,91 @@ +/** + * Copyright 2011 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.util.EnumSet; + +/** + * Specifies the set of possible permissions for a shared access table policy. + */ +public enum SharedAccessTablePermissions { + + /** + * No shared access granted. + */ + NONE((byte) 0x0), + + /** + * Permission to query entities granted. + */ + QUERY((byte) 0x1), + + /** + * Permission to add entities granted. + */ + ADD((byte) 0x2), + + /** + * Permission to modify entities granted. + */ + UPDATE((byte) 0x4), + + /** + * Permission to delete entities granted. + */ + DELETE((byte) 0x8); + + /** + * Returns the enum set representing the shared access permissions for the specified byte value. + * + * @param value + * The byte value to convert to the corresponding enum set. + * @return A <code>java.util.EnumSet</code> object that contains the <code>SharedAccessPermissions</code> values + * corresponding to the specified byte value. + */ + protected static EnumSet<SharedAccessTablePermissions> fromByte(final byte value) { + final EnumSet<SharedAccessTablePermissions> retSet = EnumSet.noneOf(SharedAccessTablePermissions.class); + + if (value == QUERY.value) { + retSet.add(QUERY); + } + + if (value == ADD.value) { + retSet.add(ADD); + } + if (value == UPDATE.value) { + retSet.add(UPDATE); + } + if (value == DELETE.value) { + retSet.add(DELETE); + } + + return retSet; + } + + /** + * Returns the value of this enum. + */ + private byte value; + + /** + * Sets the value of this enum. + * + * @param val + * The value being assigned. + */ + SharedAccessTablePermissions(final byte val) { + this.value = val; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java new file mode 100644 index 0000000000000..adfaffb35385a --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/SharedAccessTablePolicy.java @@ -0,0 +1,173 @@ +/** + * Copyright 2011 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.util.Date; +import java.util.EnumSet; + +import com.microsoft.windowsazure.services.core.storage.Constants; + +/** + * Represents a shared access policy, which specifies the start time, expiry time, and permissions for a shared access + * signature. + */ +public final class SharedAccessTablePolicy { + + /** + * Assigns shared access permissions using the specified permissions string. + * + * @param value + * A <code>String</code> that represents the shared access permissions. The string must contain one or + * more of the following values. Note they must be lowercase, and the order that they are specified must + * be in the order of "rwdl". + * <ul> + * <li><code>d</code>: Delete access.</li> + * <li><code>l</code>: List access.</li> + * <li><code>r</code>: Read access.</li> + * <li><code>w</code>: Write access.</li> + * </ul> + * + * @return A <code>java.util.EnumSet</code> object that contains {@link SharedAccessTablePermissions} values that + * represents the set of shared access permissions. + */ + public static EnumSet<SharedAccessTablePermissions> permissionsFromString(final String value) { + final char[] chars = value.toCharArray(); + final EnumSet<SharedAccessTablePermissions> retSet = EnumSet.noneOf(SharedAccessTablePermissions.class); + + for (final char c : chars) { + switch (c) { + case 'r': + retSet.add(SharedAccessTablePermissions.QUERY); + break; + case 'a': + retSet.add(SharedAccessTablePermissions.ADD); + break; + case 'u': + retSet.add(SharedAccessTablePermissions.UPDATE); + break; + case 'd': + retSet.add(SharedAccessTablePermissions.DELETE); + break; + default: + throw new IllegalArgumentException("value"); + } + } + + return retSet; + } + + /** + * Converts the permissions specified for the shared access policy to a string. + * + * @param permissions + * A {@link SharedAccessTablePermissions} object that represents the shared access permissions. + * + * @return A <code>String</code> that represents the shared access permissions in the "rwdl" format, which is + * described at {@link SharedAccessTablePermissions#permissionsFromString}. + */ + public static String permissionsToString(final EnumSet<SharedAccessTablePermissions> permissions) { + if (permissions == null) { + return Constants.EMPTY_STRING; + } + + // The service supports a fixed order => rwdl + final StringBuilder builder = new StringBuilder(); + + if (permissions.contains(SharedAccessTablePermissions.QUERY)) { + builder.append("r"); + } + + if (permissions.contains(SharedAccessTablePermissions.ADD)) { + builder.append("a"); + } + + if (permissions.contains(SharedAccessTablePermissions.UPDATE)) { + builder.append("u"); + } + + if (permissions.contains(SharedAccessTablePermissions.DELETE)) { + builder.append("d"); + } + + return builder.toString(); + } + + /** + * The permissions for a shared access signature associated with this shared access policy. + */ + private EnumSet<SharedAccessTablePermissions> permissions; + + /** + * The expiry time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessExpiryTime; + + /** + * The start time for a shared access signature associated with this shared access policy. + */ + private Date sharedAccessStartTime; + + /** + * Creates an instance of the <code>SharedAccessTablePolicy</code> class. + * */ + public SharedAccessTablePolicy() { + // Empty Default Ctor + } + + /** + * @return the permissions + */ + public EnumSet<SharedAccessTablePermissions> getPermissions() { + return this.permissions; + } + + /** + * @return the sharedAccessExpiryTime + */ + public Date getSharedAccessExpiryTime() { + return this.sharedAccessExpiryTime; + } + + /** + * @return the sharedAccessStartTime + */ + public Date getSharedAccessStartTime() { + return this.sharedAccessStartTime; + } + + /** + * @param permissions + * the permissions to set + */ + public void setPermissions(final EnumSet<SharedAccessTablePermissions> permissions) { + this.permissions = permissions; + } + + /** + * @param sharedAccessExpiryTime + * the sharedAccessExpiryTime to set + */ + public void setSharedAccessExpiryTime(final Date sharedAccessExpiryTime) { + this.sharedAccessExpiryTime = sharedAccessExpiryTime; + } + + /** + * @param sharedAccessStartTime + * the sharedAccessStartTime to set + */ + public void setSharedAccessStartTime(final Date sharedAccessStartTime) { + this.sharedAccessStartTime = sharedAccessStartTime; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java new file mode 100644 index 0000000000000..3333e2b3c54f1 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableAccessPolicyResponse.java @@ -0,0 +1,88 @@ +/** + * Copyright 2011 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.text.ParseException; + +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import com.microsoft.windowsazure.services.core.storage.AccessPolicyResponseBase; +import com.microsoft.windowsazure.services.core.storage.Constants; +import com.microsoft.windowsazure.services.core.storage.utils.Utility; + +/** + * RESERVED FOR INTERNAL USE. A class used to parse SharedAccessPolicies from an input stream. + */ +final class TableAccessPolicyResponse extends AccessPolicyResponseBase<SharedAccessTablePolicy> { + + /** + * Initializes the AccessPolicyResponse object + * + * @param stream + * the input stream to read error details from. + */ + public TableAccessPolicyResponse(final InputStream stream) { + super(stream); + } + + /** + * Populates the object from the XMLStreamReader, reader must be at Start element of AccessPolicy. + * + * @param xmlr + * the XMLStreamReader object + * @throws XMLStreamException + * if there is a parsing exception + * @throws ParseException + * if a date value is not correctly encoded + */ + @Override + protected SharedAccessTablePolicy readPolicyFromXML(final XMLStreamReader xmlr) throws XMLStreamException, + ParseException { + int eventType = xmlr.getEventType(); + + xmlr.require(XMLStreamConstants.START_ELEMENT, null, Constants.ACCESS_POLICY); + final SharedAccessTablePolicy retPolicy = new SharedAccessTablePolicy(); + + while (xmlr.hasNext()) { + eventType = xmlr.next(); + + if (eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT) { + final String name = xmlr.getName().toString(); + + if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.PERMISSION)) { + retPolicy.setPermissions(SharedAccessTablePolicy.permissionsFromString(Utility + .readElementFromXMLReader(xmlr, Constants.PERMISSION))); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.START)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.START); + retPolicy.setSharedAccessStartTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.START_ELEMENT && name.equals(Constants.EXPIRY)) { + final String tempString = Utility.readElementFromXMLReader(xmlr, Constants.EXPIRY); + retPolicy.setSharedAccessExpiryTime(Utility.parseISO8061LongDateFromString(tempString)); + } + else if (eventType == XMLStreamConstants.END_ELEMENT && name.equals(Constants.ACCESS_POLICY)) { + break; + } + } + } + + xmlr.require(XMLStreamConstants.END_ELEMENT, null, Constants.ACCESS_POLICY); + return retPolicy; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java index 596e519612c30..162edd2bdab23 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableBatchOperation.java @@ -391,7 +391,7 @@ public ArrayList<TableResult> execute(final CloudTableClient client, final Table 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.getEndpoint(), + final HttpURLConnection request = TableRequest.batch(client.getTransformedEndPoint(opContext), options.getTimeoutIntervalInMs(), batchID, null, options, opContext); client.getCredentials().signRequestLite(request, -1L, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java index 8b8ec3e422bc7..3bffd84a306b6 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableOperation.java @@ -256,7 +256,7 @@ private TableResult performDelete(final CloudTableClient client, final String ta public TableResult execute(final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.delete(client.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.delete(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(isTableEntry, tableIdentity, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); @@ -323,7 +323,7 @@ private TableResult performInsert(final CloudTableClient client, final String ta @Override public TableResult execute(final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.insert(client.getEndpoint(), tableName, + 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); @@ -410,7 +410,7 @@ private TableResult performMerge(final CloudTableClient client, final String tab public TableResult execute(final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.merge(client.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.merge(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); @@ -476,7 +476,7 @@ private TableResult performUpdate(final CloudTableClient client, final String ta public TableResult execute(final CloudTableClient client, final TableOperation operation, final OperationContext opContext) throws Exception { - final HttpURLConnection request = TableRequest.update(client.getEndpoint(), tableName, + final HttpURLConnection request = TableRequest.update(client.getTransformedEndPoint(opContext), tableName, generateRequestIdentity(false, null, false), operation.getEntity().getEtag(), options.getTimeoutIntervalInMs(), null, options, opContext); diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java new file mode 100644 index 0000000000000..4ba7bd8bdfa1c --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TablePermissions.java @@ -0,0 +1,55 @@ +/** + * Copyright 2011 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.util.HashMap; + +/** + * Represents the permissions for a container. + */ + +public final class TablePermissions { + + /** + * Gets the set of shared access policies for the table. + */ + private HashMap<String, SharedAccessTablePolicy> sharedAccessPolicies; + + /** + * Creates an instance of the <code>TablePermissions</code> class. + */ + public TablePermissions() { + this.sharedAccessPolicies = new HashMap<String, SharedAccessTablePolicy>(); + } + + /** + * Returns the set of shared access policies for the table. + * + * @return A <code>HashMap</code> object of {@link SharedAccessTablePolicy} objects that represent the set of shared + * access policies for the table. + */ + public HashMap<String, SharedAccessTablePolicy> getSharedAccessPolicies() { + return this.sharedAccessPolicies; + } + + /** + * @param sharedAccessPolicies + * the sharedAccessPolicies to set + */ + public void setSharedAccessPolicies(final HashMap<String, SharedAccessTablePolicy> sharedAccessPolicies) { + this.sharedAccessPolicies = sharedAccessPolicies; + } +} \ No newline at end of file diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java index 4642bec1b298a..782aa98ef84e7 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/client/TableRequest.java @@ -16,9 +16,16 @@ package com.microsoft.windowsazure.services.table.client; import java.io.IOException; +import java.io.StringWriter; import java.net.HttpURLConnection; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; import com.microsoft.windowsazure.services.core.storage.Constants; import com.microsoft.windowsazure.services.core.storage.OperationContext; @@ -423,6 +430,152 @@ protected static HttpURLConnection update(final URI rootUri, final String tableN return retConnection; } + /** + * Sets the ACL for the table. , Sign with length of aclBytes. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + * */ + public static HttpURLConnection setAcl(final URI rootUri, final int timeoutInMs, final OperationContext opContext) + throws IOException, URISyntaxException, StorageException { + + UriQueryBuilder queryBuilder = new UriQueryBuilder(); + queryBuilder.add("comp", "acl"); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, timeoutInMs, queryBuilder, + opContext); + retConnection.setRequestMethod("PUT"); + retConnection.setDoOutput(true); + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + TableConstants.HeaderConstants.ATOMPUB_TYPE); + + return retConnection; + } + + /** + * Writes a collection of shared access policies to the specified stream in XML format. + * + * @param sharedAccessPolicies + * A collection of shared access policies + * @param outWriter + * an sink to write the output to. + * @throws XMLStreamException + */ + public static void writeSharedAccessIdentifiersToStream( + final HashMap<String, SharedAccessTablePolicy> sharedAccessPolicies, final StringWriter outWriter) + throws XMLStreamException { + Utility.assertNotNull("sharedAccessPolicies", sharedAccessPolicies); + Utility.assertNotNull("outWriter", outWriter); + + final XMLOutputFactory xmlOutFactoryInst = XMLOutputFactory.newInstance(); + final XMLStreamWriter xmlw = xmlOutFactoryInst.createXMLStreamWriter(outWriter); + + if (sharedAccessPolicies.keySet().size() > Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS) { + final String errorMessage = String + .format("Too many %d shared access policy identifiers provided. Server does not support setting more than %d on a single container.", + sharedAccessPolicies.keySet().size(), Constants.MAX_SHARED_ACCESS_POLICY_IDENTIFIERS); + + throw new IllegalArgumentException(errorMessage); + } + + // default is UTF8 + xmlw.writeStartDocument(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIERS_ELEMENT); + + for (final Entry<String, SharedAccessTablePolicy> entry : sharedAccessPolicies.entrySet()) { + final SharedAccessTablePolicy policy = entry.getValue(); + xmlw.writeStartElement(Constants.SIGNED_IDENTIFIER_ELEMENT); + + // Set the identifier + xmlw.writeStartElement(Constants.ID); + xmlw.writeCharacters(entry.getKey()); + xmlw.writeEndElement(); + + xmlw.writeStartElement(Constants.ACCESS_POLICY); + + // Set the Start Time + xmlw.writeStartElement(Constants.START); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessStartTime())); + // end Start + xmlw.writeEndElement(); + + // Set the Expiry Time + xmlw.writeStartElement(Constants.EXPIRY); + xmlw.writeCharacters(Utility.getUTCTimeOrEmpty(policy.getSharedAccessExpiryTime())); + // end Expiry + xmlw.writeEndElement(); + + // Set the Permissions + xmlw.writeStartElement(Constants.PERMISSION); + xmlw.writeCharacters(SharedAccessTablePolicy.permissionsToString(policy.getPermissions())); + // end Permission + xmlw.writeEndElement(); + + // end AccessPolicy + xmlw.writeEndElement(); + // end SignedIdentifier + xmlw.writeEndElement(); + } + + // end SignedIdentifiers + xmlw.writeEndElement(); + // end doc + xmlw.writeEndDocument(); + } + + /** + * Constructs a web request to return the ACL for this table. Sign with no length specified. + * + * @param rootUri + * A <code>java.net.URI</code> containing an absolute URI to the resource. + * @param tableName + * The name of the table. + * @param timeoutInMs + * The server timeout interval in milliseconds. + * @param options + * A {@link TableRequestOptions} object that specifies execution options such as retry policy and timeout + * settings for the operation. Specify <code>null</code> to use the request options specified on the + * {@link CloudTableClient}. + * @param opContext + * An {@link OperationContext} object for tracking the current operation. Specify <code>null</code> to + * safely ignore operation context. + * + * @return a HttpURLConnection configured for the operation. + * @throws StorageException + */ + public static HttpURLConnection getAcl(final URI rootUri, final String tableName, final int timeoutInMs, + final OperationContext opContext) throws IOException, URISyntaxException, StorageException { + + UriQueryBuilder queryBuilder = new UriQueryBuilder(); + queryBuilder.add("comp", "acl"); + + final HttpURLConnection retConnection = BaseRequest.createURLConnection(rootUri, timeoutInMs, queryBuilder, + opContext); + retConnection.setRequestMethod("GET"); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, TableConstants.HeaderConstants.ACCEPT_TYPE); + retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, "UTF8"); + retConnection.setRequestProperty(TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION, + TableConstants.HeaderConstants.MAX_DATA_SERVICE_VERSION_VALUE); + retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, + TableConstants.HeaderConstants.ATOMPUB_TYPE); + + return retConnection; + } + /** * Private Default Constructor. */ diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java new file mode 100644 index 0000000000000..bd0671e3ece19 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/BlobTestBase.java @@ -0,0 +1,73 @@ +/** + * Copyright 2011 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.blob.client; + +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Blob Test Base + * Blob test refactoring will be done in future. + */ +public class BlobTestBase { + public static boolean USE_DEV_FABRIC = false; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + + protected static CloudStorageAccount httpAcc; + protected static CloudBlobClient bClient; + protected static String testSuiteContainerName = generateRandomContainerName(); + protected static byte[] testData = new byte[] { 1, 2, 3, 4 }; + + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + + // UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); + + if (USE_DEV_FABRIC) { + httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); + } + else { + httpAcc = CloudStorageAccount.parse(CLOUD_ACCOUNT_HTTP); + } + + bClient = httpAcc.createCloudBlobClient(); + testSuiteContainerName = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(testSuiteContainerName); + container.create(); + } + + @AfterClass + public static void teardown() throws StorageException, URISyntaxException { + CloudBlobContainer container = bClient.getContainerReference(testSuiteContainerName); + container.delete(); + } + + protected static String generateRandomContainerName() { + String containerName = "container" + UUID.randomUUID().toString(); + return containerName.replace("-", ""); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java new file mode 100644 index 0000000000000..c9da0ea84377e --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/client/CloudBlobContainerTests.java @@ -0,0 +1,501 @@ +/** + * Copyright 2011 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.blob.client; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Random; +import java.util.TimeZone; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.AccessCondition; +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Client Tests + */ +public class CloudBlobContainerTests extends BlobTestBase { + /** + * test SharedAccess of container. + * + * @XSCLCaseName ContainerInfoSharedAccess + */ + @Test + public void testContainerSaS() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, IOException { + + String name = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(name); + container.create(); + + CloudBlockBlob blob = container.getBlockBlobReference("test"); + blob.upload(new ByteArrayInputStream(new byte[100]), 100); + + SharedAccessBlobPolicy sp1 = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.WRITE, + SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE), 300); + SharedAccessBlobPolicy sp2 = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 300); + BlobContainerPermissions perms = new BlobContainerPermissions(); + + perms.getSharedAccessPolicies().put("full", sp1); + perms.getSharedAccessPolicies().put("readlist", sp2); + container.uploadPermissions(perms); + + String containerReadListSas = container.generateSharedAccessSignature(sp2, null); + CloudBlobContainer readListContainer = bClient.getContainerReference(container.getUri().toString() + "?" + + containerReadListSas); + Assert.assertEquals(StorageCredentialsSharedAccessSignature.class.toString(), readListContainer + .getServiceClient().getCredentials().getClass().toString()); + + CloudBlockBlob blobFromSasContainer = readListContainer.getBlockBlobReference("test"); + blobFromSasContainer.download(new ByteArrayOutputStream()); + container.deleteIfExists(); + } + + @Test + public void testBlobSaS() throws InvalidKeyException, IllegalArgumentException, StorageException, + URISyntaxException, IOException { + + String name = generateRandomContainerName(); + CloudBlobContainer container = bClient.getContainerReference(name); + container.create(); + + CloudBlockBlob blob = container.getBlockBlobReference("test"); + blob.upload(new ByteArrayInputStream(new byte[100]), 100); + + SharedAccessBlobPolicy sp = createSharedAccessPolicy( + EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.LIST), 300); + BlobContainerPermissions perms = new BlobContainerPermissions(); + + perms.getSharedAccessPolicies().put("readperm", sp); + container.uploadPermissions(perms); + + CloudBlockBlob sasBlob = new CloudBlockBlob(new URI(blob.getUri().toString() + "?" + + blob.generateSharedAccessSignature(null, "readperm"))); + sasBlob.download(new ByteArrayOutputStream()); + container.deleteIfExists(); + } + + public final static SharedAccessBlobPolicy createSharedAccessPolicy(EnumSet<SharedAccessBlobPermissions> sap, + int expireTimeInSeconds) { + + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + cal.setTime(new Date()); + cal.add(Calendar.SECOND, expireTimeInSeconds); + SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy(); + policy.setPermissions(sap); + policy.setSharedAccessExpiryTime(cal.getTime()); + return policy; + + } + + @Test + public void testContainerGetSetPermission() throws StorageException, URISyntaxException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + + BlobContainerPermissions expectedPermissions; + BlobContainerPermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new BlobContainerPermissions(); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + newContainer.uploadPermissions(expectedPermissions); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessBlobPolicy policy1 = new SharedAccessBlobPolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessBlobPermissions.READ, SharedAccessBlobPermissions.DELETE, + SharedAccessBlobPermissions.LIST, SharedAccessBlobPermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + newContainer.uploadPermissions(expectedPermissions); + testPermissions = newContainer.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + static void assertTablePermissionsEqual(BlobContainerPermissions expected, BlobContainerPermissions actual) { + HashMap<String, SharedAccessBlobPolicy> expectedPolicies = expected.getSharedAccessPolicies(); + HashMap<String, SharedAccessBlobPolicy> actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessBlobPolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessBlobPolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testContainerAcquireLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer leaseContainer1 = bClient.getContainerReference(name); + leaseContainer1.create(); + String proposedLeaseId1 = UUID.randomUUID().toString(); + + name = "leased" + generateRandomContainerName(); + CloudBlobContainer leaseContainer2 = bClient.getContainerReference(name); + leaseContainer2.create(); + String proposedLeaseId2 = UUID.randomUUID().toString(); + + try { + // 15 sec + + OperationContext operationContext1 = new OperationContext(); + leaseContainer1.acquireLease(15, proposedLeaseId1, null /*access condition*/, + null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + //infinite + String leaseId1; + String leaseId2; + OperationContext operationContext2 = new OperationContext(); + leaseId1 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2, + null /*access condition*/, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + leaseId2 = leaseContainer2.acquireLease(null /* infinite lease */, proposedLeaseId2); + Assert.assertEquals(leaseId1, leaseId2); + + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId1); + leaseContainer1.releaseLease(condition); + leaseContainer1.deleteIfExists(); + + condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId2); + leaseContainer2.releaseLease(condition); + leaseContainer2.deleteIfExists(); + } + } + + @Test + public void testContainerReleaseLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + + try { + // 15 sec + String proposedLeaseId = UUID.randomUUID().toString(); + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.releaseLease(condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.releaseLease(condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + @Test + public void testContainerBreakLease() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + String proposedLeaseId = UUID.randomUUID().toString(); + + try { + // 5 sec + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.breakLease(0, condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + Thread.sleep(15 * 1000); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.breakLease(0, condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId); + newContainer.releaseLease(condition); + newContainer.deleteIfExists(); + } + } + + @Test + public void testContainerRenewLeaseTest() throws StorageException, URISyntaxException, InterruptedException { + String name = "leased" + generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + String proposedLeaseId = UUID.randomUUID().toString(); + + try { + // 5 sec + String leaseId = newContainer.acquireLease(15, proposedLeaseId); + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext1 = new OperationContext(); + newContainer.renewLease(condition, null/* BlobRequestOptions */, operationContext1); + Assert.assertTrue(operationContext1.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + newContainer.releaseLease(condition); + + //infinite + proposedLeaseId = UUID.randomUUID().toString(); + leaseId = newContainer.acquireLease(null /* infinite lease */, proposedLeaseId); + condition = new AccessCondition(); + condition.setLeaseID(leaseId); + OperationContext operationContext2 = new OperationContext(); + newContainer.renewLease(condition, null/* BlobRequestOptions */, operationContext2); + Assert.assertTrue(operationContext2.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + finally { + // cleanup + AccessCondition condition = new AccessCondition(); + condition.setLeaseID(proposedLeaseId); + newContainer.releaseLease(condition); + newContainer.deleteIfExists(); + } + } + + @Test + public void testBlobLeaseAcquireAndRelease() throws URISyntaxException, StorageException, IOException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer leasedContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = leasedContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + OperationContext operationContext = new OperationContext(); + final String leaseID = blobRef.acquireLease(15, null /*access condition*/, null /*proposed lease id */, + null/* BlobRequestOptions */, operationContext); + final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_CREATED); + + // Try to upload without lease + try { + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + } + catch (final StorageException ex) { + Assert.assertEquals(ex.getHttpStatusCode(), 412); + Assert.assertEquals(ex.getErrorCode(), StorageErrorCodeStrings.LEASE_ID_MISSING); + } + + // Try to upload with lease + blobRef.upload(new ByteArrayInputStream(buff), -1, leaseCondition, options, null); + + // Release lease + blobRef.releaseLease(leaseCondition); + + // now upload with no lease specified. + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + } + + @Test + public void testBlobLeaseBreak() throws URISyntaxException, StorageException, IOException, InterruptedException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer existingContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = existingContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + String leaseID = blobRef.acquireLease(null, null); + + OperationContext operationContext = new OperationContext(); + final AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + blobRef.breakLease(0, leaseCondition, null/* BlobRequestOptions */, operationContext); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED); + } + + @Test + public void testBlobLeaseRenew() throws URISyntaxException, StorageException, IOException, InterruptedException { + final int length = 128; + final Random randGenerator = new Random(); + final byte[] buff = new byte[length]; + randGenerator.nextBytes(buff); + + String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000)); + blobName = blobName.replace('-', '_'); + + final CloudBlobContainer existingContainer = bClient.getContainerReference(testSuiteContainerName); + final CloudBlob blobRef = existingContainer.getBlockBlobReference(blobName); + final BlobRequestOptions options = new BlobRequestOptions(); + + blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, null); + + // Get Lease + final String leaseID = blobRef.acquireLease(15, null); + Thread.sleep(1000); + + AccessCondition leaseCondition = AccessCondition.generateLeaseCondition(leaseID); + OperationContext operationContext = new OperationContext(); + blobRef.renewLease(leaseCondition, null/* BlobRequestOptions */, operationContext); + Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_OK); + } + + static String setLeasedState(CloudBlobContainer container, int leaseTime) throws StorageException { + String leaseId = UUID.randomUUID().toString(); + setUnleasedState(container); + return container.acquireLease(leaseTime, leaseId); + } + + static void setUnleasedState(CloudBlobContainer container) throws StorageException { + if (!container.createIfNotExist()) { + try { + container.breakLease(0); + } + catch (StorageException e) { + if (e.getHttpStatusCode() != HttpURLConnection.HTTP_BAD_REQUEST) { + throw e; + } + } + } + } + + @Test + public void testCopyFromBlob() throws StorageException, URISyntaxException, IOException, InterruptedException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + CloudBlob originalBlob = newContainer.getBlockBlobReference("newblob"); + originalBlob.upload(new ByteArrayInputStream(testData), testData.length); + + try { + CloudBlob copyBlob = newContainer.getBlockBlobReference(originalBlob.getName() + "copyed"); + copyBlob.copyFromBlob(originalBlob); + Thread.sleep(1000); + copyBlob.downloadAttributes(); + Assert.assertNotNull(copyBlob.copyState); + Assert.assertNotNull(copyBlob.copyState.getCopyId()); + Assert.assertNotNull(copyBlob.copyState.getCompletionTime()); + Assert.assertNotNull(copyBlob.copyState.getSource()); + Assert.assertFalse(copyBlob.copyState.getBytesCopied() == 0); + Assert.assertFalse(copyBlob.copyState.getTotalBytes() == 0); + for (final ListBlobItem blob : newContainer.listBlobs()) { + CloudBlob blobFromList = ((CloudBlob) blob); + blobFromList.downloadAttributes(); + } + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } + + @Test + public void testCopyFromBlobAbortTest() throws StorageException, URISyntaxException, IOException, + InterruptedException { + String name = generateRandomContainerName(); + CloudBlobContainer newContainer = bClient.getContainerReference(name); + newContainer.create(); + CloudBlob originalBlob = newContainer.getBlockBlobReference("newblob"); + byte[] data = new byte[16 * 1024 * 1024]; + Random r = new Random(); + r.nextBytes(data); + originalBlob.upload(new ByteArrayInputStream(data), testData.length); + + try { + CloudBlob copyBlob = newContainer.getBlockBlobReference(originalBlob.getName() + "copyed"); + copyBlob.copyFromBlob(originalBlob); + + try { + copyBlob.abortCopy(copyBlob.copyState.getCopyId()); + } + catch (StorageException e) { + if (!e.getErrorCode().contains("NoPendingCopyOperation")) { + throw e; + } + } + } + finally { + // cleanup + newContainer.deleteIfExists(); + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java new file mode 100644 index 0000000000000..a8405f5abd7f9 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueClientTests.java @@ -0,0 +1,218 @@ +/** + * Copyright 2011 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.queue.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.UUID; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageCredentials; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +public final class CloudQueueClientTests extends QueueTestBase { + + @Test + public void testQueueClientConstructor() throws URISyntaxException, StorageException { + StorageCredentials credentials = httpAcc.getCredentials(); + URI baseAddressUri = new URI(httpAcc.getQueueEndpoint().toString()); + + CloudQueueClient queueClient = new CloudQueueClient(baseAddressUri, credentials); + + Assert.assertEquals(baseAddressUri, queueClient.getEndpoint()); + Assert.assertEquals(credentials, queueClient.getCredentials()); + } + + @Test + public void testQueueClientConstructorInvalidParam() throws URISyntaxException, StorageException { + StorageCredentials credentials = httpAcc.getCredentials(); + try { + new CloudQueueClient(null, credentials); + Assert.fail(); + } + catch (IllegalArgumentException e) { + + } + + try { + char[] name = new char[2000]; + new CloudQueueClient(new URI(name.toString()), credentials); + Assert.fail(); + } + catch (URISyntaxException e) { + + } + } + + @Test + public void testListQueuesSmallNumber() throws URISyntaxException, StorageException { + int initialCount = 0; + + for (CloudQueue queue : qClient.listQueues()) { + initialCount++; + } + + HashMap<String, String> metadata1 = new HashMap<String, String>(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + + for (int i = 0; i < 25; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), UUID.randomUUID().toString() + .toLowerCase()), qClient); + q.setMetadata(metadata1); + q.create(); + } + + int count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + + Assert.assertEquals(count, initialCount + 25); + + String perfix = "prefixtest" + UUID.randomUUID().toString().substring(0, 8).toLowerCase(); + for (int i = 0; i < 25; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), perfix + + UUID.randomUUID().toString().toLowerCase()), qClient); + HashMap<String, String> metadata2 = new HashMap<String, String>(); + metadata2.put("tags", q.getName()); + q.setMetadata(metadata2); + q.create(); + } + + count = 0; + for (CloudQueue queue : qClient.listQueues(perfix, QueueListingDetails.METADATA, null, null)) { + count++; + Assert.assertTrue(queue.getMetadata().size() == 1 + && queue.getMetadata().get("tags").equals(queue.getName())); + } + + Assert.assertEquals(count, 25); + } + + @Test + public void testListQueuesAndListQueuesSegmentedLargeNumber() throws URISyntaxException, StorageException { + int count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + + int totalLimit = 5005; + if (count < totalLimit) { + + NumberFormat myFormat = NumberFormat.getInstance(); + myFormat.setMinimumIntegerDigits(4); + + for (int i = 0; i < totalLimit - count; i++) { + String sub = myFormat.format(i); + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), + String.format("listqueue" + sub.replace(",", ""))), qClient); + q.createIfNotExist(); + } + } + + count = 0; + for (CloudQueue queue : qClient.listQueues()) { + count++; + } + Assert.assertTrue(count >= totalLimit); + + ResultSegment<CloudQueue> segment = qClient.listQueuesSegmented(); + Assert.assertTrue(segment.getLength() == 5000); + Assert.assertTrue(segment.getContinuationToken() != null); + } + + @Test + public void testListQueuesSegmented() throws URISyntaxException, StorageException { + String perfix = "segment" + UUID.randomUUID().toString().substring(0, 8).toLowerCase(); + + HashMap<String, String> metadata1 = new HashMap<String, String>(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + + for (int i = 0; i < 35; i++) { + CloudQueue q = new CloudQueue(AppendQueueName(httpAcc.getQueueEndpoint(), perfix + + UUID.randomUUID().toString().toLowerCase()), qClient); + q.setMetadata(metadata1); + q.create(); + } + + ResultSegment<CloudQueue> segment1 = qClient.listQueuesSegmented(perfix); + Assert.assertTrue(segment1.getLength() == 35); + + ResultSegment<CloudQueue> segment2 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 5, null, + null, null); + Assert.assertTrue(segment2.getLength() == 5); + + int totalRoundTrip = 1; + while (segment2.getHasMoreResults()) { + segment2 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 5, + segment2.getContinuationToken(), null, null); + Assert.assertTrue(segment2.getLength() == 5); + totalRoundTrip++; + } + + Assert.assertTrue(totalRoundTrip == 7); + + ResultSegment<CloudQueue> segment3 = qClient.listQueuesSegmented(perfix, QueueListingDetails.NONE, 0, null, + null, null); + Assert.assertTrue(segment3.getLength() == 35); + } + + @Test + public void testListQueuesEqual() throws URISyntaxException, StorageException { + int count1 = 0; + for (CloudQueue queue : qClient.listQueues()) { + count1++; + } + + int count2 = 0; + for (CloudQueue queue : qClient.listQueues("")) { + count2++; + } + + int count3 = 0; + for (CloudQueue queue : qClient.listQueues(null)) { + count3++; + } + + Assert.assertEquals(count1, count2); + Assert.assertEquals(count1, count3); + } + + @Test + public void testTimeout() throws URISyntaxException, StorageException { + Assert.assertTrue(qClient.getTimeoutInMs() == 30 * 1000); + qClient.setTimeoutInMs(60 * 1000); + Assert.assertTrue(qClient.getTimeoutInMs() == 60 * 1000); + } + + static String AppendQueueName(URI baseURI, String queueName) throws URISyntaxException { + if (baseURI == null) + return queueName; + + String baseAddress = baseURI.toString(); + if (baseAddress.endsWith("/")) { + return baseAddress + queueName; + } + else { + return baseAddress + "/" + queueName; + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java new file mode 100644 index 0000000000000..156474bf037f8 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/CloudQueueTests.java @@ -0,0 +1,1273 @@ +/** + * Copyright 2011 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.queue.client; + +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.Calendar; +import java.util.Date; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Random; +import java.util.UUID; + +import javax.xml.stream.XMLStreamException; + +import junit.framework.Assert; + +import org.junit.Test; + +import com.microsoft.windowsazure.services.core.storage.OperationContext; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; +import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Table Client Tests + */ +public class CloudQueueTests extends QueueTestBase { + @Test + public void queueGetSetPermissionTest() throws StorageException, URISyntaxException { + String name = generateRandomQueueName(); + CloudQueue newQueue = qClient.getQueueReference(name); + newQueue.create(); + + QueuePermissions expectedPermissions; + QueuePermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new QueuePermissions(); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + newQueue.uploadPermissions(expectedPermissions); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ, + SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD, + SharedAccessQueuePermissions.UPDATE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + newQueue.uploadPermissions(expectedPermissions); + testPermissions = newQueue.downloadPermissions(); + assertQueuePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + newQueue.deleteIfExists(); + } + } + + @Test + public void testQueueSAS() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomQueueName(); + CloudQueue newQueue = qClient.getQueueReference(name); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("sas queue test")); + + QueuePermissions expectedPermissions; + + try { + expectedPermissions = new QueuePermissions(); + // Add a policy, check setting and getting. + SharedAccessQueuePolicy policy1 = new SharedAccessQueuePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + String identifier = UUID.randomUUID().toString(); + + policy1.setPermissions(EnumSet.of(SharedAccessQueuePermissions.READ, + SharedAccessQueuePermissions.PROCESSMESSAGES, SharedAccessQueuePermissions.ADD, + SharedAccessQueuePermissions.UPDATE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + newQueue.uploadPermissions(expectedPermissions); + + CloudQueueClient queueClientFromIdentifierSAS = getQueueClientForSas(newQueue, null, identifier); + CloudQueue identifierSasQueue = queueClientFromIdentifierSAS.getQueueReference(newQueue.getName()); + + identifierSasQueue.downloadAttributes(); + identifierSasQueue.exists(); + + identifierSasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = identifierSasQueue.retrieveMessage(); + identifierSasQueue.deleteMessage(message1); + + CloudQueueClient queueClientFromPolicySAS = getQueueClientForSas(newQueue, policy1, null); + CloudQueue policySasQueue = queueClientFromPolicySAS.getQueueReference(newQueue.getName()); + policySasQueue.exists(); + policySasQueue.downloadAttributes(); + + policySasQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message2 = policySasQueue.retrieveMessage(); + policySasQueue.deleteMessage(message2); + } + finally { + // cleanup + newQueue.deleteIfExists(); + } + } + + private CloudQueueClient getQueueClientForSas(CloudQueue queue, SharedAccessQueuePolicy policy, + String accessIdentifier) throws InvalidKeyException, StorageException { + String sasString = queue.generateSharedAccessSignature(policy, accessIdentifier); + return new CloudQueueClient(qClient.getEndpoint(), new StorageCredentialsSharedAccessSignature(sasString)); + } + + static void assertQueuePermissionsEqual(QueuePermissions expected, QueuePermissions actual) { + HashMap<String, SharedAccessQueuePolicy> expectedPolicies = expected.getSharedAccessPolicies(); + HashMap<String, SharedAccessQueuePolicy> actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessQueuePolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessQueuePolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testQueueClientConstructor() throws URISyntaxException, StorageException { + String queueName = "queue"; + String baseAddress = AppendQueueName(qClient.getEndpoint(), queueName); + CloudQueue queue1 = new CloudQueue(baseAddress, qClient); + Assert.assertEquals(queueName, queue1.getName()); + Assert.assertTrue(queue1.getUri().toString().endsWith(baseAddress)); + Assert.assertEquals(qClient, queue1.getServiceClient()); + + CloudQueue queue2 = new CloudQueue(new URI(AppendQueueName(qClient.getEndpoint(), queueName)), qClient); + + Assert.assertEquals(queueName, queue2.getName()); + Assert.assertEquals(qClient, queue2.getServiceClient()); + + CloudQueue queue3 = new CloudQueue(queueName, qClient); + Assert.assertEquals(queueName, queue3.getName()); + Assert.assertEquals(qClient, queue3.getServiceClient()); + } + + @Test + public void testGetMetadata() throws URISyntaxException, StorageException { + HashMap<String, String> metadata = new HashMap<String, String>(); + metadata.put("ExistingMetadata", "ExistingMetadataValue"); + queue.setMetadata(metadata); + queue.uploadMetadata(); + queue.downloadAttributes(); + Assert.assertEquals(queue.getMetadata().get("ExistingMetadata"), "ExistingMetadataValue"); + Assert.assertTrue(queue.getMetadata().containsKey("ExistingMetadata")); + + HashMap<String, String> empytMetadata = null; + queue.setMetadata(empytMetadata); + queue.uploadMetadata(); + queue.downloadAttributes(); + Assert.assertTrue(queue.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadata() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap<String, String> metadata1 = new HashMap<String, String>(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey("ExistingMetadata1")); + + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().containsKey("ExistingMetadata1")); + } + + @Test + public void testUploadMetadataNullInput() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap<String, String> metadata1 = new HashMap<String, String>(); + String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); + metadata1.put(key, "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey(key)); + + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().containsKey(key)); + + queue.setMetadata(null); + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadataClearExisting() throws URISyntaxException, StorageException { + CloudQueue queueForGet = new CloudQueue(queue.getUri(), queue.getServiceClient()); + + HashMap<String, String> metadata1 = new HashMap<String, String>(); + String key = "ExistingMetadata1" + UUID.randomUUID().toString().replace("-", ""); + metadata1.put(key, "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + + queueForGet.downloadAttributes(); + Assert.assertFalse(queueForGet.getMetadata().containsKey(key)); + + HashMap<String, String> metadata2 = new HashMap<String, String>(); + queue.setMetadata(metadata2); + queue.uploadMetadata(); + queueForGet.downloadAttributes(); + Assert.assertTrue(queueForGet.getMetadata().size() == 0); + } + + @Test + public void testUploadMetadataNotFound() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.uploadMetadata(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueCreate() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + try { + HashMap<String, String> metadata1 = new HashMap<String, String>(); + metadata1.put("ExistingMetadata1", "ExistingMetadataValue1"); + queue.setMetadata(metadata1); + queue.create(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_CONFLICT); + + } + + queue.downloadAttributes(); + OperationContext createQueueContext2 = new OperationContext(); + queue.create(null, createQueueContext2); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + queue.delete(); + } + + @Test + public void testQueueCreateAlreadyExists() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + queue.create(null, createQueueContext1); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext createQueueContext2 = new OperationContext(); + queue.create(null, createQueueContext2); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void testQueueCreateAfterDelete() throws URISyntaxException, StorageException { + + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + try { + queue.create(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testQueueCreateIfNotExists() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext)); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + } + + @Test + public void testQueueCreateIfNotExistsAfterCreate() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext createQueueContext2 = new OperationContext(); + Assert.assertFalse(queue.createIfNotExist(null, createQueueContext2)); + Assert.assertEquals(createQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + @Test + public void testQueueCreateIfNotExistsAfterDelete() throws URISyntaxException, StorageException { + + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext1 = new OperationContext(); + Assert.assertTrue(queue.createIfNotExist(null, createQueueContext1)); + Assert.assertEquals(createQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + try { + queue.createIfNotExist(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testQueueDelete() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + OperationContext deleteQueueContext = new OperationContext(); + queue.delete(null, deleteQueueContext); + Assert.assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + try { + queue.downloadAttributes(); + Assert.fail(); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + + queue.delete(); + } + + @Test + public void testDeleteQueueIfExists() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + Assert.assertFalse(queue.deleteIfExists()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + Assert.assertTrue(queue.deleteIfExists()); + + try { + queue.create(); + Assert.fail("Queue CreateIfNotExists did not throw exception while trying to create a queue in BeingDeleted State"); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex.getHttpStatusCode(), + HttpURLConnection.HTTP_CONFLICT); + Assert.assertEquals("Expected 409 Exception, QueueBeingDeleted not thrown", ex + .getExtendedErrorInformation().getErrorCode(), StorageErrorCodeStrings.QUEUE_BEING_DELETED); + } + } + + @Test + public void testDeleteNonExistingQueue() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + final OperationContext existQueueContext1 = new OperationContext(); + Assert.assertTrue(!queue.exists(null, existQueueContext1)); + Assert.assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + + try { + queue.delete(); + Assert.fail("Queue delete no exsiting queue. "); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + } + + @Test + public void testQueueExist() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + final OperationContext existQueueContext1 = new OperationContext(); + Assert.assertTrue(!queue.exists(null, existQueueContext1)); + Assert.assertEquals(existQueueContext1.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final OperationContext existQueueContext2 = new OperationContext(); + Assert.assertTrue(queue.exists(null, existQueueContext2)); + Assert.assertEquals(existQueueContext2.getLastResult().getStatusCode(), HttpURLConnection.HTTP_OK); + } + + @Test + public void testClearMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + queue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue.addMessage(message2); + + int count = 0; + for (CloudQueueMessage m : queue.peekMessages(32)) { + count++; + } + + Assert.assertTrue(count == 2); + + OperationContext oc = new OperationContext(); + queue.clear(null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + count = 0; + for (CloudQueueMessage m : queue.peekMessages(32)) { + count++; + } + + Assert.assertTrue(count == 0); + } + + public void testClearMessagesEmptyQueue() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + queue.create(); + queue.clear(); + queue.delete(); + } + + public void testClearMessagesNotFound() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + try { + queue.clear(); + Assert.fail(); + } + catch (StorageException ex) { + Assert.assertEquals("Expected 404 Exception", ex.getHttpStatusCode(), HttpURLConnection.HTTP_NOT_FOUND); + } + } + + @Test + public void testAddMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = UUID.randomUUID().toString(); + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(message.getMessageContentAsString(), msgContent); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + + queue.delete(); + } + + @Test + public void testAddMessageToNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + + String messageContent = "messagetest"; + CloudQueueMessage message1 = new CloudQueueMessage(messageContent); + + try { + newQueue.addMessage(message1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueUnicodeAndXmlMessageTest() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = "好<?xml version= 1.0 encoding= utf-8 ?>"; + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(message.getMessageContentAsString(), msgContent); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + //Assert.assertEquals(message.getMessageContentAsByte(), msgFromRetrieve1.getMessageContentAsByte()); + + queue.delete(); + } + + @Test + public void testAddMessageLargeMessageInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final Random rand = new Random(); + + byte[] content = new byte[64 * 1024]; + rand.nextBytes(content); + CloudQueueMessage message1 = new CloudQueueMessage(new String(content)); + + try { + queue.addMessage(message1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testAddMessageWithVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage m1 = queue.retrieveMessage(); + Date d1 = m1.getExpirationTime(); + queue.deleteMessage(m1); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + queue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage m2 = queue.retrieveMessage(); + Date d2 = m2.getExpirationTime(); + queue.deleteMessage(m2); + Assert.assertTrue(d1.before(d2)); + } + + @Test + public void testAddMessageNullMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + try { + queue.addMessage(null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testAddMessageSpecialVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + CloudQueueMessage message = new CloudQueueMessage("test"); + queue.addMessage(message, 1, 0, null, null); + queue.addMessage(message, 7 * 24 * 60 * 60, 0, null, null); + queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60 - 1, null, null); + + try { + queue.addMessage(message, -1, 0, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 0, -1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 7 * 24 * 60 * 60, 7 * 24 * 60 * 60, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 7 * 24 * 60 * 60 + 1, 0, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.addMessage(message, 0, 7 * 24 * 60 * 60 + 1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.CONTENT), null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testDeleteMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage message : newQueue.retrieveMessages(32)) { + OperationContext deleteQueueContext = new OperationContext(); + newQueue.deleteMessage(message, null, deleteQueueContext); + Assert.assertEquals(deleteQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + + Assert.assertTrue(newQueue.retrieveMessage() == null); + } + + @Test + public void testQueueCreateAddingMetaData() throws URISyntaxException, StorageException { + final CloudQueue queue = qClient.getQueueReference(UUID.randomUUID().toString().toLowerCase()); + + final HashMap<String, String> metadata = new HashMap<String, String>(5); + for (int i = 0; i < 5; i++) { + metadata.put("key" + i, "value" + i); + } + + queue.setMetadata(metadata); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + } + + @Test + public void testDeleteMessageNullMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + try { + queue.deleteMessage(null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(); + Date expirationTime1 = message1.getExpirationTime(); + Date insertionTime1 = message1.getInsertionTime(); + Date nextVisibleTime1 = message1.getNextVisibleTime(); + newQueue.deleteMessage(message1); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message2 = newQueue.retrieveMessage(); + Date expirationTime2 = message2.getExpirationTime(); + Date insertionTime2 = message2.getInsertionTime(); + Date nextVisibleTime2 = message2.getNextVisibleTime(); + newQueue.deleteMessage(message2); + Assert.assertTrue(expirationTime1.before(expirationTime2)); + Assert.assertTrue(insertionTime1.before(insertionTime2)); + Assert.assertTrue(nextVisibleTime1.before(nextVisibleTime2)); + } + + @Test + public void testRetrieveMessageNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.retrieveMessage(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testRetrieveMessageInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + + try { + queue.retrieveMessage(-1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessage(7 * 24 * 3600 + 1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessagesFromEmptyQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + for (CloudQueueMessage m : newQueue.retrieveMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + } + + @Test + public void testRetrieveMessagesNonFound() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.retrieveMessages(1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testDequeueCountIncreases() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + newQueue.addMessage(new CloudQueueMessage("message"), 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(1, null, null); + Assert.assertTrue(message1.getDequeueCount() == 1); + + for (int i = 2; i < 5; i++) { + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + CloudQueueMessage message2 = newQueue.retrieveMessage(1, null, null); + Assert.assertTrue(message2.getDequeueCount() == i); + } + + } + + @Test + public void testRetrieveMessageSpecialVisibilityTimeout() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + + try { + queue.retrieveMessage(-1, null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testRetrieveMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage m : newQueue.retrieveMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() != null); + } + } + + @Test + public void testRetrieveMessagesInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.createIfNotExist(); + + for (int i = 0; i < 33; i++) { + queue.addMessage(new CloudQueueMessage("test" + i)); + } + + queue.retrieveMessages(1, 1, null, null); + queue.retrieveMessages(32, 1, null, null); + + try { + queue.retrieveMessages(-1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessages(0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.retrieveMessages(33); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testPeekMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage msg = newQueue.peekMessage(); + Assert.assertTrue(msg.getId() != null); + Assert.assertTrue(msg.getPopReceipt() == null); + + newQueue.delete(); + } + + @Test + public void testPeekMessages() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + newQueue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + newQueue.addMessage(message2); + + for (CloudQueueMessage m : newQueue.peekMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + + newQueue.delete(); + } + + @Test + public void testPeekMessagesInvalidInput() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.createIfNotExist(); + + for (int i = 0; i < 33; i++) { + queue.addMessage(new CloudQueueMessage("test" + i)); + } + + queue.peekMessages(1); + queue.peekMessages(32); + + try { + queue.peekMessages(-1); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.peekMessages(0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + try { + queue.peekMessages(33); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testPeekMessageNonExistingQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.peekMessage(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testPeekMessagesNonFound() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.peekMessages(1); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testPeekMessagesFromEmptyQueue() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + + for (CloudQueueMessage m : newQueue.peekMessages(32)) { + Assert.assertTrue(m.getId() != null); + Assert.assertTrue(m.getPopReceipt() == null); + } + } + + @Test + public void testUpdateMessage() throws URISyntaxException, StorageException, UnsupportedEncodingException { + + String messageContent = "messagetest"; + CloudQueueMessage message1 = new CloudQueueMessage(messageContent); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage(messageContent); + queue.addMessage(message2); + + String newMesage = message1.getMessageContentAsString() + "updated"; + + for (CloudQueueMessage message : queue.retrieveMessages(32)) { + OperationContext oc = new OperationContext(); + message.setMessageContent(newMesage); + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + CloudQueueMessage messageFromGet = queue.retrieveMessage(); + Assert.assertEquals(messageFromGet.getMessageContentAsString(), messageContent); + } + } + + @Test + public void testUpdateMessageFullPass() throws URISyntaxException, StorageException, UnsupportedEncodingException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + newQueue.create(); + CloudQueueMessage message = new CloudQueueMessage("message"); + newQueue.addMessage(message, 20, 0, null, null); + CloudQueueMessage message1 = newQueue.retrieveMessage(); + String popreceipt1 = message1.getPopReceipt(); + Date NextVisibleTim1 = message1.getNextVisibleTime(); + newQueue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null); + String popreceipt2 = message1.getPopReceipt(); + Date NextVisibleTim2 = message1.getNextVisibleTime(); + Assert.assertTrue(popreceipt2 != popreceipt1); + Assert.assertTrue(NextVisibleTim1.before(NextVisibleTim2)); + + try { + Thread.sleep(2000); + } + catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + String newMesage = message.getMessageContentAsString() + "updated"; + message.setMessageContent(newMesage); + OperationContext oc = new OperationContext(); + newQueue.updateMessage(message1, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + String popreceipt3 = message1.getPopReceipt(); + Date NextVisibleTim3 = message1.getNextVisibleTime(); + Assert.assertTrue(popreceipt3 != popreceipt2); + Assert.assertTrue(NextVisibleTim2.before(NextVisibleTim3)); + + Assert.assertTrue(newQueue.retrieveMessage() == null); + + newQueue.updateMessage(message1, 0, EnumSet.of(MessageUpdateFields.VISIBILITY), null, null); + + CloudQueueMessage messageFromGet = newQueue.retrieveMessage(); + Assert.assertEquals(messageFromGet.getMessageContentAsString(), message1.getMessageContentAsString()); + } + + @Test + public void testUpdateMessageWithContentChange() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + + CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue.addMessage(message1); + + CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue.addMessage(message2); + + for (CloudQueueMessage message : queue.retrieveMessages(32)) { + OperationContext oc = new OperationContext(); + message.setMessageContent(message.getMessageContentAsString() + "updated"); + queue.updateMessage(message, 100, EnumSet.of(MessageUpdateFields.CONTENT), null, oc); + Assert.assertEquals(oc.getLastResult().getStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + } + } + + @Test + public void testUpdateMessageNullMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + try { + queue.updateMessage(null, 0); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + } + + @Test + public void testUpdateMessageInvalidMessage() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(null, null); + + CloudQueueMessage message = new CloudQueueMessage("test"); + queue.addMessage(message, 1, 0, null, null); + + try { + queue.updateMessage(message, 0, EnumSet.of(MessageUpdateFields.CONTENT), null, null); + Assert.fail(); + } + catch (final IllegalArgumentException e) { + } + + queue.delete(); + } + + @Test + public void testGetApproximateMessageCount() throws URISyntaxException, StorageException, + UnsupportedEncodingException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + Assert.assertTrue(queue.getApproximateMessageCount() == 0); + queue.addMessage(new CloudQueueMessage("message1")); + queue.addMessage(new CloudQueueMessage("message2")); + Assert.assertTrue(queue.getApproximateMessageCount() == 0); + queue.downloadAttributes(); + Assert.assertTrue(queue.getApproximateMessageCount() == 2); + queue.delete(); + } + + @Test + public void testShouldEncodeMessage() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + queue.create(); + + String msgContent = UUID.randomUUID().toString(); + final CloudQueueMessage message = new CloudQueueMessage(msgContent); + queue.setShouldEncodeMessage(true); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve1 = queue.retrieveMessage(); + Assert.assertEquals(msgFromRetrieve1.getMessageContentAsString(), msgContent); + queue.deleteMessage(msgFromRetrieve1); + + queue.setShouldEncodeMessage(false); + queue.addMessage(message); + CloudQueueMessage msgFromRetrieve2 = queue.retrieveMessage(); + Assert.assertEquals(msgFromRetrieve2.getMessageContentAsString(), msgContent); + queue.deleteMessage(msgFromRetrieve2); + + queue.setShouldEncodeMessage(true); + } + + @Test + public void testQueueDownloadAttributes() throws URISyntaxException, StorageException, + UnsupportedEncodingException, XMLStreamException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + + final CloudQueue queue1 = qClient.getQueueReference(queueName); + queue1.create(); + + final CloudQueueMessage message1 = new CloudQueueMessage("messagetest1"); + queue1.addMessage(message1); + + final CloudQueueMessage message2 = new CloudQueueMessage("messagetest2"); + queue1.addMessage(message2); + + final HashMap<String, String> metadata = new HashMap<String, String>(5); + int sum = 5; + for (int i = 0; i < sum; i++) { + metadata.put("key" + i, "value" + i); + } + + queue1.setMetadata(metadata); + queue1.uploadMetadata(); + + final CloudQueue queue2 = qClient.getQueueReference(queueName); + queue2.downloadAttributes(); + + System.out.println(queue2.getApproximateMessageCount()); + + int count = 0; + for (final String s : queue2.getMetadata().keySet()) { + count++; + System.out.println(s + ":" + queue2.getMetadata().get(s)); + } + + Assert.assertEquals(count, sum); + + queue1.delete(); + } + + @Test + public void testQueueDownloadAttributesNotFound() throws URISyntaxException, StorageException { + String queueName = UUID.randomUUID().toString().toLowerCase(); + CloudQueue newQueue = qClient.getQueueReference(queueName); + try { + newQueue.downloadAttributes(); + Assert.fail(); + } + catch (StorageException e) { + Assert.assertTrue(e.getHttpStatusCode() == HttpURLConnection.HTTP_NOT_FOUND); + + } + } + + @Test + public void testQueueUpdateMetaData() throws URISyntaxException, StorageException { + final String queueName = UUID.randomUUID().toString().toLowerCase(); + final CloudQueue queue = qClient.getQueueReference(queueName); + Assert.assertEquals(queueName, queue.getName()); + + final OperationContext createQueueContext = new OperationContext(); + queue.create(null, createQueueContext); + Assert.assertEquals(createQueueContext.getLastResult().getStatusCode(), HttpURLConnection.HTTP_CREATED); + + final HashMap<String, String> metadata = new HashMap<String, String>(5); + for (int i = 0; i < 5; i++) { + metadata.put("key" + i, "value" + i); + } + + queue.setMetadata(metadata); + queue.uploadMetadata(); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java new file mode 100644 index 0000000000000..feb5b1ceb4a10 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/queue/client/QueueTestBase.java @@ -0,0 +1,87 @@ +/** + * Copyright 2011 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.queue.client; + +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidKeyException; +import java.util.UUID; + +import org.junit.AfterClass; +import org.junit.BeforeClass; + +import com.microsoft.windowsazure.services.core.storage.CloudStorageAccount; +import com.microsoft.windowsazure.services.core.storage.StorageException; + +/** + * Queue Test Base + * Queue test refactoring will be done in future. + */ +public class QueueTestBase { + public static boolean USE_DEV_FABRIC = false; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + + protected static CloudStorageAccount httpAcc; + protected static CloudQueueClient qClient; + protected static String testSuiteQueueName = generateRandomQueueName(); + protected static CloudQueue queue; + + @BeforeClass + public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { + + // UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); + + if (USE_DEV_FABRIC) { + httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); + } + else { + httpAcc = CloudStorageAccount.parse(CLOUD_ACCOUNT_HTTP); + } + + qClient = httpAcc.createCloudQueueClient(); + testSuiteQueueName = generateRandomQueueName(); + queue = qClient.getQueueReference(testSuiteQueueName); + queue.create(); + } + + @AfterClass + public static void teardown() throws StorageException, URISyntaxException { + CloudQueue queue = qClient.getQueueReference(testSuiteQueueName); + queue.delete(); + } + + protected static String generateRandomQueueName() { + String queueName = "queue" + UUID.randomUUID().toString(); + return queueName.replace("-", ""); + } + + static String AppendQueueName(URI baseURI, String queueName) throws URISyntaxException { + if (baseURI == null) + return queueName; + + String baseAddress = baseURI.toString(); + if (baseAddress.endsWith("/")) { + return baseAddress + queueName; + } + else { + return baseAddress + "/" + queueName; + } + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java index 8060002ca8ae2..86c5056cf9f73 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableClientTests.java @@ -17,15 +17,24 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URISyntaxException; +import java.security.InvalidKeyException; import java.text.DecimalFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.UUID; import junit.framework.Assert; import org.junit.Test; import com.microsoft.windowsazure.services.core.storage.ResultSegment; +import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature; import com.microsoft.windowsazure.services.core.storage.StorageException; /** @@ -35,10 +44,12 @@ public class TableClientTests extends TableTestBase { @Test public void listTablesSegmented() throws IOException, URISyntaxException, StorageException { String tableBaseName = generateRandomTableName(); + ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -72,7 +83,8 @@ public void listTablesSegmented() throws IOException, URISyntaxException, Storag } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @@ -83,7 +95,8 @@ public void listTablesSegmentedNoPrefix() throws IOException, URISyntaxException ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -124,7 +137,8 @@ public void listTablesSegmentedNoPrefix() throws IOException, URISyntaxException } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @@ -135,7 +149,8 @@ public void listTablesWithIterator() throws IOException, URISyntaxException, Sto ArrayList<String> tables = new ArrayList<String>(); for (int m = 0; m < 20; m++) { String name = String.format("%s%s", tableBaseName, new DecimalFormat("#0000").format(m)); - tClient.createTable(name); + CloudTable table = tClient.getTableReference(name); + table.create(); tables.add(name); } @@ -180,21 +195,23 @@ public void listTablesWithIterator() throws IOException, URISyntaxException, Sto } finally { for (String s : tables) { - tClient.deleteTable(s); + CloudTable table = tClient.getTableReference(s); + table.delete(); } } } @Test - public void tableCreateAndAttemptCreateOnceExists() throws StorageException { + public void tableCreateAndAttemptCreateOnceExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); + table.create(); + Assert.assertTrue(table.exists()); // Should fail as it already exists try { - tClient.createTable(tableName); + table.create(); fail(); } catch (StorageException ex) { @@ -203,84 +220,419 @@ public void tableCreateAndAttemptCreateOnceExists() throws StorageException { } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableCreateExistsAndDelete() throws StorageException { + public void tableCreateExistsAndDelete() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.deleteTableIfExists(tableName)); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); + Assert.assertTrue(table.deleteIfExists()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableCreateIfNotExists() throws StorageException { + public void tableCreateIfNotExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); try { - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertFalse(tClient.createTableIfNotExists(tableName)); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); + Assert.assertFalse(table.createIfNotExist()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableDeleteIfExists() throws StorageException { + public void tableDeleteIfExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); - Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + Assert.assertFalse(table.deleteIfExists()); - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.deleteTableIfExists(tableName)); - Assert.assertFalse(tClient.deleteTableIfExists(tableName)); + table.create(); + Assert.assertTrue(table.exists()); + Assert.assertTrue(table.deleteIfExists()); + Assert.assertFalse(table.deleteIfExists()); } @Test - public void tableDeleteWhenExistAndNotExists() throws StorageException { + public void tableDeleteWhenExistAndNotExists() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + try { // Should fail as it doesnt already exists try { - tClient.deleteTable(tableName); + table.delete(); fail(); } catch (StorageException ex) { Assert.assertEquals(ex.getMessage(), "Not Found"); } - tClient.createTable(tableName); - Assert.assertTrue(tClient.doesTableExist(tableName)); - tClient.deleteTable(tableName); - Assert.assertFalse(tClient.doesTableExist(tableName)); + table.create(); + Assert.assertTrue(table.exists()); + table.delete(); + Assert.assertFalse(table.exists()); } finally { - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } @Test - public void tableDoesTableExist() throws StorageException { + public void tableDoesTableExist() throws StorageException, URISyntaxException { String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + try { - Assert.assertFalse(tClient.doesTableExist(tableName)); - Assert.assertTrue(tClient.createTableIfNotExists(tableName)); - Assert.assertTrue(tClient.doesTableExist(tableName)); + Assert.assertFalse(table.exists()); + Assert.assertTrue(table.createIfNotExist()); + Assert.assertTrue(table.exists()); } finally { // cleanup - tClient.deleteTableIfExists(tableName); + table.deleteIfExists(); } } + + @Test + public void tableGetSetPermissionTest() throws StorageException, URISyntaxException { + String tableName = generateRandomTableName(); + CloudTable table = tClient.getTableReference(tableName); + table.create(); + + TablePermissions expectedPermissions; + TablePermissions testPermissions; + + try { + // Test new permissions. + expectedPermissions = new TablePermissions(); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Test setting empty permissions. + table.uploadPermissions(expectedPermissions); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(UUID.randomUUID().toString(), policy1); + + table.uploadPermissions(expectedPermissions); + testPermissions = table.downloadPermissions(); + assertTablePermissionsEqual(expectedPermissions, testPermissions); + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + static void assertTablePermissionsEqual(TablePermissions expected, TablePermissions actual) { + HashMap<String, SharedAccessTablePolicy> expectedPolicies = expected.getSharedAccessPolicies(); + HashMap<String, SharedAccessTablePolicy> actualPolicies = actual.getSharedAccessPolicies(); + Assert.assertEquals("SharedAccessPolicies.Count", expectedPolicies.size(), actualPolicies.size()); + for (String name : expectedPolicies.keySet()) { + Assert.assertTrue("Key" + name + " doesn't exist", actualPolicies.containsKey(name)); + SharedAccessTablePolicy expectedPolicy = expectedPolicies.get(name); + SharedAccessTablePolicy actualPolicy = actualPolicies.get(name); + Assert.assertEquals("Policy: " + name + "\tPermissions\n", expectedPolicy.getPermissions().toString(), + actualPolicy.getPermissions().toString()); + Assert.assertEquals("Policy: " + name + "\tStartDate\n", expectedPolicy.getSharedAccessStartTime() + .toString(), actualPolicy.getSharedAccessStartTime().toString()); + Assert.assertEquals("Policy: " + name + "\tExpireDate\n", expectedPolicy.getSharedAccessExpiryTime() + .toString(), actualPolicy.getSharedAccessExpiryTime().toString()); + + } + + } + + @Test + public void testTableSASFromIdentifier() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomTableName(); + CloudTable table = tClient.getTableReference(name); + table.create(); + + try { + TablePermissions expectedPermissions = new TablePermissions(); + String identifier = UUID.randomUUID().toString(); + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + table.uploadPermissions(expectedPermissions); + + // Insert 500 entities in Batches to query + for (int i = 0; i < 5; i++) { + TableBatchOperation batch = new TableBatchOperation(); + + for (int j = 0; j < 100; j++) { + class1 ent = generateRandomEnitity("javatables_batch_" + Integer.toString(i)); + ent.setRowKey(String.format("%06d", j)); + batch.insert(ent); + } + + tClient.execute(name, batch); + } + + CloudTableClient tableClientFromIdentifierSAS = getTableForSas(table, null, identifier, null, null, null, + null); + + { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery<class1> query = TableQuery.from(name, class1.class).where( + String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); + + int count = 0; + + for (class1 ent : tableClientFromIdentifierSAS.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + } + + { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("jxscl_odata"); + baseEntity.setRowKey(UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tableClientFromIdentifierSAS.execute(name, + TableOperation.insertOrMerge(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tableClientFromIdentifierSAS.execute(name, TableOperation.insertOrMerge(secondEntity)); + + // Retrieve entity + TableResult queryResult = tableClientFromIdentifierSAS.execute(name, TableOperation.retrieve( + baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + @Test + public void testTableSASFromPermission() throws StorageException, URISyntaxException, InvalidKeyException { + String name = generateRandomTableName(); + CloudTable table = tClient.getTableReference(name); + table.create(); + + try { + TablePermissions expectedPermissions = new TablePermissions(); + String identifier = UUID.randomUUID().toString(); + // Add a policy, check setting and getting. + SharedAccessTablePolicy policy1 = new SharedAccessTablePolicy(); + Calendar now = GregorianCalendar.getInstance(); + policy1.setSharedAccessStartTime(now.getTime()); + now.add(Calendar.MINUTE, 10); + policy1.setSharedAccessExpiryTime(now.getTime()); + + policy1.setPermissions(EnumSet.of(SharedAccessTablePermissions.ADD, SharedAccessTablePermissions.QUERY, + SharedAccessTablePermissions.UPDATE, SharedAccessTablePermissions.DELETE)); + expectedPermissions.getSharedAccessPolicies().put(identifier, policy1); + + table.uploadPermissions(expectedPermissions); + + // Insert 500 entities in Batches to query + for (int i = 0; i < 5; i++) { + TableBatchOperation batch = new TableBatchOperation(); + + for (int j = 0; j < 100; j++) { + class1 ent = generateRandomEnitity("javatables_batch_" + Integer.toString(i)); + ent.setRowKey(String.format("%06d", j)); + batch.insert(ent); + } + + tClient.execute(name, batch); + } + + CloudTableClient tableClientFromPermission = getTableForSas(table, policy1, null, "javatables_batch_0", + "0", "javatables_batch_9", "9"); + CloudTableClient tableClientFromPermissionJustPks = getTableForSas(table, policy1, null, + "javatables_batch_0", null, "javatables_batch_9", null); + + { + class1 randEnt = TableTestBase.generateRandomEnitity(null); + TableQuery<class1> query = TableQuery.from(name, class1.class).where( + String.format("(PartitionKey eq '%s') and (RowKey ge '%s')", "javatables_batch_1", "000050")); + + int count = 0; + + for (class1 ent : tableClientFromPermission.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + + count = 0; + + for (class1 ent : tableClientFromPermissionJustPks.execute(query)) { + Assert.assertEquals(ent.getA(), randEnt.getA()); + Assert.assertEquals(ent.getB(), randEnt.getB()); + Assert.assertEquals(ent.getC(), randEnt.getC()); + Assert.assertEquals(ent.getPartitionKey(), "javatables_batch_1"); + Assert.assertEquals(ent.getRowKey(), String.format("%06d", count + 50)); + count++; + } + + Assert.assertEquals(count, 50); + } + + { + class1 baseEntity = new class1(); + baseEntity.setA("foo_A"); + baseEntity.setB("foo_B"); + baseEntity.setC("foo_C"); + baseEntity.setD(new byte[] { 0, 1, 2 }); + baseEntity.setPartitionKey("javatables_batch_0" + UUID.randomUUID().toString()); + baseEntity.setRowKey("0" + UUID.randomUUID().toString()); + + class2 secondEntity = new class2(); + secondEntity.setL("foo_L"); + secondEntity.setM("foo_M"); + secondEntity.setN("foo_N"); + secondEntity.setO("foo_O"); + secondEntity.setPartitionKey(baseEntity.getPartitionKey()); + secondEntity.setRowKey(baseEntity.getRowKey()); + secondEntity.setEtag(baseEntity.getEtag()); + + // Insert or merge Entity - ENTITY DOES NOT EXIST NOW. + TableResult insertResult = tableClientFromPermission.execute(name, + TableOperation.insertOrMerge(baseEntity)); + + Assert.assertEquals(insertResult.getHttpStatusCode(), HttpURLConnection.HTTP_NO_CONTENT); + + // Insert or replace Entity - ENTITY EXISTS -> WILL REPLACE + tableClientFromPermission.execute(name, TableOperation.insertOrMerge(secondEntity)); + + // Retrieve entity + TableResult queryResult = tableClientFromPermission.execute(name, TableOperation.retrieve( + baseEntity.getPartitionKey(), baseEntity.getRowKey(), DynamicTableEntity.class)); + + DynamicTableEntity retrievedEntity = queryResult.<DynamicTableEntity> getResultAsType(); + + Assert.assertNotNull("Property A", retrievedEntity.getProperties().get("A")); + Assert.assertEquals(baseEntity.getA(), retrievedEntity.getProperties().get("A").getValueAsString()); + + Assert.assertNotNull("Property B", retrievedEntity.getProperties().get("B")); + Assert.assertEquals(baseEntity.getB(), retrievedEntity.getProperties().get("B").getValueAsString()); + + Assert.assertNotNull("Property C", retrievedEntity.getProperties().get("C")); + Assert.assertEquals(baseEntity.getC(), retrievedEntity.getProperties().get("C").getValueAsString()); + + Assert.assertNotNull("Property D", retrievedEntity.getProperties().get("D")); + Assert.assertTrue(Arrays.equals(baseEntity.getD(), retrievedEntity.getProperties().get("D") + .getValueAsByteArray())); + + // Validate New properties exist + Assert.assertNotNull("Property L", retrievedEntity.getProperties().get("L")); + Assert.assertEquals(secondEntity.getL(), retrievedEntity.getProperties().get("L").getValueAsString()); + + Assert.assertNotNull("Property M", retrievedEntity.getProperties().get("M")); + Assert.assertEquals(secondEntity.getM(), retrievedEntity.getProperties().get("M").getValueAsString()); + + Assert.assertNotNull("Property N", retrievedEntity.getProperties().get("N")); + Assert.assertEquals(secondEntity.getN(), retrievedEntity.getProperties().get("N").getValueAsString()); + + Assert.assertNotNull("Property O", retrievedEntity.getProperties().get("O")); + Assert.assertEquals(secondEntity.getO(), retrievedEntity.getProperties().get("O").getValueAsString()); + } + } + finally { + // cleanup + table.deleteIfExists(); + } + } + + private CloudTableClient getTableForSas(CloudTable table, SharedAccessTablePolicy policy, String accessIdentifier, + String startPk, String startRk, String endPk, String endRk) throws InvalidKeyException, StorageException { + String sasString = table + .generateSharedAccessSignature(policy, accessIdentifier, startPk, startRk, endPk, endRk); + return new CloudTableClient(tClient.getEndpoint(), new StorageCredentialsSharedAccessSignature(sasString)); + } } diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java index b30f200fa61a9..d235ed326cc69 100644 --- a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/client/TableTestBase.java @@ -35,7 +35,7 @@ */ public class TableTestBase { public static boolean USE_DEV_FABRIC = false; - public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=http;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; + public static final String CLOUD_ACCOUNT_HTTP = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; public static final String CLOUD_ACCOUNT_HTTPS = "DefaultEndpointsProtocol=https;AccountName=[ACCOUNT NAME];AccountKey=[ACCOUNT KEY]"; public static class class1 extends TableServiceEntity { @@ -568,11 +568,11 @@ public static class1 generateRandomEnitity(String pk) { @BeforeClass public static void setup() throws URISyntaxException, StorageException, InvalidKeyException { - // UNCOMMENT TO USE FIDDLER - // System.setProperty("http.proxyHost", "localhost"); - // System.setProperty("http.proxyPort", "8888"); - // System.setProperty("https.proxyHost", "localhost"); - // System.setProperty("https.proxyPort", "8888"); + //UNCOMMENT TO USE FIDDLER + System.setProperty("http.proxyHost", "localhost"); + System.setProperty("http.proxyPort", "8888"); + System.setProperty("https.proxyHost", "localhost"); + System.setProperty("https.proxyPort", "8888"); if (USE_DEV_FABRIC) { httpAcc = CloudStorageAccount.getDevelopmentStorageAccount(); } @@ -584,12 +584,14 @@ public static void setup() throws URISyntaxException, StorageException, InvalidK tClient = httpAcc.createCloudTableClient(); qClient = httpAcc.createCloudQueueClient(); testSuiteTableName = generateRandomTableName(); - tClient.createTable(testSuiteTableName); + CloudTable table = tClient.getTableReference(testSuiteTableName); + table.create(); } @AfterClass - public static void teardown() throws StorageException { - tClient.deleteTable(testSuiteTableName); + public static void teardown() throws StorageException, URISyntaxException { + CloudTable table = tClient.getTableReference(testSuiteTableName); + table.delete(); } protected static String generateRandomTableName() { From 783b6becdd323c3b2ef402665e31caa5de975424 Mon Sep 17 00:00:00 2001 From: Jason Cooke <jcooke@microsoft.com> Date: Wed, 27 Jun 2012 11:38:20 -0700 Subject: [PATCH 74/76] Adding license to POM and CommaStringBuilder --- microsoft-azure-api/pom.xml | 14 ++++++++++++++ .../services/core/utils/CommaStringBuilder.java | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index b9cb8f3ad694d..4b6d77bf04e95 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -1,3 +1,17 @@ +<!-- + Copyright 2011 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. +--> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java index 3623028e5ef16..ac8b9f45c519c 100644 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java @@ -1,3 +1,17 @@ +/** + * Copyright 2012 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.core.utils; import java.util.List; From 41da386095a004548a023e7ce6dd97e5d0ec78b1 Mon Sep 17 00:00:00 2001 From: Joost de Nijs <joostden@microsoft.com> Date: Mon, 2 Jul 2012 14:44:30 -0700 Subject: [PATCH 75/76] incrementing version number --- microsoft-azure-api/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml index 4b6d77bf04e95..7f328aed9576a 100644 --- a/microsoft-azure-api/pom.xml +++ b/microsoft-azure-api/pom.xml @@ -17,7 +17,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>com.microsoft.windowsazure</groupId> <artifactId>microsoft-windowsazure-api</artifactId> - <version>0.2.2</version> + <version>0.3.0</version> <packaging>jar</packaging> <name>Microsoft Windows Azure Client API</name> From 3106f96166213f5b29b41c8017c06e74eb56b13c Mon Sep 17 00:00:00 2001 From: dverma <deepakv@microsoft.com> Date: Mon, 2 Jul 2012 16:00:13 -0700 Subject: [PATCH 76/76] Updated the version number in the via maeven section --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76a899e45b209..07c59ee7dee66 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ within your project you can also have them installed by the Java package manager <dependency> <groupId>com.microsoft.windowsazure</groupId> <artifactId>microsoft-windowsazure-api</artifactId> - <version>0.1.0</version> + <version>0.3.0</version> </dependency> ##Minimum Requirements