From 6a25ef7d48334d8a891d54b30ae9f086e42737ac Mon Sep 17 00:00:00 2001 From: Renaud Paquay Date: Wed, 7 Dec 2011 10:34:55 -0800 Subject: [PATCH 01/22] 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 000000000000..484cd066f2a0 --- /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 000000000000..49c90e0ddf3d --- /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 000000000000..9a849047abc0 --- /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 000000000000..2e732e4fc1d6 --- /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 000000000000..9cfbe21f43ff --- /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 000000000000..094fe429145a --- /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 000000000000..6aadc0650f3c --- /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 000000000000..5e965269030a --- /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 000000000000..30ab7196d448 --- /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 000000000000..2e068e4fe9df --- /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 000000000000..e42240885c15 --- /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/22] 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 6481932c7cde..9cbc9ae72bf2 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 0134195d5c3a..37ff1321d5d9 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 a562b7c2122e..f7aae2cd9188 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 9a849047abc0..6e759d01d433 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 9cfbe21f43ff..ffa302d15640 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 6aadc0650f3c..a771f2e57c70 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 5e965269030a..9e1fdd24e27b 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 000000000000..e60e10bfe4ef --- /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 000000000000..0215444e944b --- /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 cc7087393981..07fdb1c4e7b6 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 000000000000..8b7b18382d59 --- /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 000000000000..2c67bbce8c0e --- /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 e7bf06b034de..7739acd3e733 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/22] 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 ffa302d15640..19a5cc737bc5 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 9e1fdd24e27b..52b3eb6bbdac 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/22] 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 197537a9db82..8d9d63bc8972 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 3907ac20db40..5bba95ef4599 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 39e67d931a38..af860e50936d 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 209a345d26a0..a5f02e025637 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 9cbc9ae72bf2..9072ead43564 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 000000000000..3623028e5ef1 --- /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 0610eb8d7bc9..20d9046c5358 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 95fce9e9dbeb..9cebe855ee2a 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 484cd066f2a0..c36841928b06 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 6e759d01d433..4d9776bde42c 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 000000000000..bd8439e5ee43 --- /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 000000000000..9c44869f23f9 --- /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 a771f2e57c70..48e2f4e675da 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 52b3eb6bbdac..17326178a952 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 000000000000..0e97c0193ab5 --- /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 000000000000..25547090206a --- /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 000000000000..4c38168ca295 --- /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 000000000000..655a53838e75 --- /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 000000000000..2978c465cebd --- /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 000000000000..25f042b564f0 --- /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 000000000000..8c4d857624e3 --- /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 000000000000..7c915cf53cb4 --- /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 e60e10bfe4ef..d4896fa67498 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 0215444e944b..3816ffe5899a 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 000000000000..917cecc3409c --- /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 000000000000..3e0efd984bff --- /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 000000000000..3cf03c788639 --- /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 2c67bbce8c0e..434b10be1be3 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/22] 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 a5f02e025637..08d7d6ea9ce8 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 000000000000..e7582255ef59 --- /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 c36841928b06..21ad0784df4b 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 4d9776bde42c..8fc4caefeadc 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 bd8439e5ee43..c1943ebdcd1e 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 000000000000..b062b3394ab9 --- /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 9c44869f23f9..8504c88fa349 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 19a5cc737bc5..79305ce758c8 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 48e2f4e675da..64215de5c604 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 17326178a952..4a2dbc92c29f 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 000000000000..5a82435b9337 --- /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 000000000000..de13eee4740f --- /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 2978c465cebd..de199b24f075 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 000000000000..666fc9634da3 --- /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 000000000000..1e90530e04f0 --- /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 3cf03c788639..9aebd223e75b 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 434b10be1be3..3ecfde9f2b5a 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/22] 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 3ecfde9f2b5a..f07d3a1bb468 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/22] 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 8fc4caefeadc..4b1611c61e86 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 c1943ebdcd1e..0f85fcede8cb 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 64215de5c604..8737038654b6 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 4a2dbc92c29f..852545c8b775 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 de13eee4740f..ce1d6dff3933 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 000000000000..2db782e63693 --- /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 f07d3a1bb468..254678ed9088 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/22] 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 4b1611c61e86..c3a2b2724aef 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 8737038654b6..a13a87175285 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 852545c8b775..e54e867a89a7 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 000000000000..441f3020822b --- /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 254678ed9088..98e6346cf89f 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/22] 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 c3a2b2724aef..7e5d968d0874 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 a13a87175285..abde4ec8cbe5 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 e54e867a89a7..30178256e691 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 98e6346cf89f..f5ab25bf9e37 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/22] 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 7e5d968d0874..88526099a933 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 abde4ec8cbe5..df7384900baf 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 30178256e691..db969f5c182a 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 f5ab25bf9e37..5962a2b35bc8 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/22] 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 88526099a933..412c9afbdea5 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 df7384900baf..fc1ac6473d7d 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 db969f5c182a..603a0c04f1d5 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 5962a2b35bc8..35bac81f30cc 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/22] 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 412c9afbdea5..c214333e8a63 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 0f85fcede8cb..c5803bfb5411 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 fc1ac6473d7d..93a99570fdba 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 603a0c04f1d5..51fc33bbeddd 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 000000000000..34195d479504 --- /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 000000000000..55652baec98d --- /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 000000000000..3977ccc9b97d --- /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 35bac81f30cc..27420b286d08 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/22] 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 51fc33bbeddd..95558d7021dd 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 000000000000..462be9a865c6 --- /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 25547090206a..000000000000 --- 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 4c38168ca295..9dd16b1a1daa 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 000000000000..fcb3560758a2 --- /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 655a53838e75..000000000000 --- 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 8c4d857624e3..d67f47162c4d 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 000000000000..57e7f8c30b66 --- /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 7c915cf53cb4..000000000000 --- 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 55652baec98d..1de1cd47426b 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 d4896fa67498..d402cd8527cc 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 3816ffe5899a..dfd1c1b7f4aa 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 3e0efd984bff..42d4b7a870fc 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/22] 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 08d7d6ea9ce8..e796bf00794c 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 000000000000..6f514dad26d3 --- /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/22] 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 27420b286d08..cf45713dc8fd 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/22] 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 95558d7021dd..3f18d447b788 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 fcb3560758a2..f0a54f58b09b 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 000000000000..ffc8e38aef00 --- /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 cf45713dc8fd..8e12ebac2b04 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/22] 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 5da3a62a9e6c..36651b165c7c 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 21ad0784df4b..59322db2264d 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 c214333e8a63..63ec2c1b2086 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 000000000000..933d3f8aac55 --- /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 93a99570fdba..c8bf695b7cdd 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 3f18d447b788..39deca1589bf 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 000000000000..43c93c22b0f8 --- /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 000000000000..462dc4d10ef6 --- /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 8e12ebac2b04..c489c675735a 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 9aebd223e75b..aa14a71f8597 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 000000000000..c01fdeaebc3c --- /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/22] 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 9cebe855ee2a..a36de8dc0247 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 59322db2264d..ee52dd47bfb2 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 000000000000..458acf6e0cd9 --- /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 000000000000..22c099a52fee --- /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 933d3f8aac55..9d2282ab08e6 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 39deca1589bf..944bb8214424 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 43c93c22b0f8..12eb2a98314e 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 462dc4d10ef6..30a0b3beebd9 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 c489c675735a..aadff9804481 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/22] 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 aadff9804481..8fd16a9e2bb6 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/22] 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 944bb8214424..a18ffe066a21 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 12eb2a98314e..f60bfa8fb005 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/22] 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 a18ffe066a21..096c5b4ec312 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 f60bfa8fb005..f1df873ac4da 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 8fd16a9e2bb6..c8143349741d 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/22] 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 f7aae2cd9188..234581b023fb 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; } });