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/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/ContainerACLDateConverter.java deleted file mode 100644 index 209a345d26a0..000000000000 --- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2011 Microsoft Corporation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.microsoft.windowsazure.services.blob.implementation; - -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.TimeZone; - -/* - * "not quite" ISO 8601 date time conversion routines - */ -public class ContainerACLDateConverter { - // 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'"; - - public String format(Date date) { - return getFormat().format(date); - } - - 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")); - return iso8601Format; - } -} 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 new file mode 100644 index 000000000000..e796bf00794c --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java @@ -0,0 +1,59 @@ +/** + * 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 java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +/* + * "not quite" ISO 8601 date time conversion routines + */ +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 { + 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() { + DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US); + 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/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..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 @@ -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,11 +118,11 @@ 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()); - cr.getHeaders().add("Date", date); + cr.getHeaders().putSingle("Date", 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/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/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java index a562b7c2122e..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 @@ -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; @@ -25,8 +25,10 @@ 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 +39,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,6 +48,7 @@ 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); 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..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,34 +28,11 @@ public class PipelineHelpers { public static void ThrowIfError(ClientResponse r) { - if (r.getStatus() >= 300) { + if (r.getStatus() >= 400) { throw new UniformInterfaceException(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/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 new file mode 100644 index 000000000000..ee52dd47bfb2 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.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.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.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; +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 + 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); + 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/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..63ec2c1b2086 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java @@ -0,0 +1,104 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.microsoft.windowsazure.services.table; + +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; +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; +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; + + GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException; + + void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException; + + 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; + + 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; + + UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException; + + 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; + + 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) + 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; + + 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/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/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java new file mode 100644 index 000000000000..c5803bfb5411 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java @@ -0,0 +1,307 @@ +package com.microsoft.windowsazure.services.table.implementation; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +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, EdmValueConverter edmValueConverter) { + this.xmlStreamFactory = xmlStreamFactory; + this.dateFactory = dateFactory; + this.iso8601DateConverter = iso8601DateConverter; + this.edmValueConverter = edmValueConverter; + } + + 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 + } + }); + } + + public InputStream generateEntityEntry(Entity entity) { + final Entity entityTemp = entity; + return generateEntry(new PropertiesWriter() { + @Override + public void write(XMLStreamWriter writer) throws XMLStreamException { + for (Entry 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 parseTableEntries(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed"); + + List result = new ArrayList(); + 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); + } + } + + public List parseEntityEntries(InputStream stream) { + try { + XMLStreamReader xmlr = xmlStreamFactory.getReader(stream); + + expect(xmlr, XMLStreamConstants.START_DOCUMENT); + expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed"); + + 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, "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; + } + 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 { + TableEntry result = new TableEntry(); + + expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry"); + + while (!isEndElement(xmlr, "entry")) { + if (isStartElement(xmlr, "properties")) { + Map properties = parseEntryProperties(xmlr); + + result.setName((String) properties.get("TableName").getValue()); + } + else { + nextSignificant(xmlr); + } + } + + expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry"); + + 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(); + + 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; + 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/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 new file mode 100644 index 000000000000..8504c88fa349 --- /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, "UTF-8"); + } + 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/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
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 new file mode 100644 index 000000000000..9d2282ab08e6 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java @@ -0,0 +1,147 @@ +package com.microsoft.windowsazure.services.table.implementation; + +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 { + + @Inject + public MimeReaderWriter() { + } + + public MimeMultipart getMimeMultipart(List bodyPartContents) { + try { + return getMimeMultipartCore(bodyPartContents); + } + catch (MessagingException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private MimeMultipart getMimeMultipartCore(List 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()); + + // + // Build inner list of change sets containing the list of body part content + // + MimeMultipart changeSets = new MimeMultipart(new SetBoundaryMultipartDataSource(changeSet)); + + for (DataSource bodyPart : bodyPartContents) { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + + mimeBodyPart.setDataHandler(new DataHandler(bodyPart)); + mimeBodyPart.setHeader("Content-Type", bodyPart.getContentType()); + 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; + } + } + + public List 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 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 result = new ArrayList(); + 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/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java new file mode 100644 index 000000000000..79305ce758c8 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java @@ -0,0 +1,90 @@ +/** + * 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.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)); + } + //TODO: Remove or comment the following line + //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/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..c8bf695b7cdd --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java @@ -0,0 +1,491 @@ +/** + * 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.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; +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; +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; + +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("table", 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)); + } + } + + @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 { + 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)); + } + } + + @Override + public QueryTablesResult listTables() throws ServiceException { + try { + return service.listTables(); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException { + try { + return service.listTables(options); + } + catch (UniformInterfaceException e) { + throw processCatch(new ServiceException(e)); + } + catch (ClientHandlerException e) { + throw processCatch(new ServiceException(e)); + } + } + + @Override + public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException { + try { + 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)); + } + } + + @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)); + } + } + + @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 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 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 { + 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)); + } + } + + @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)); + } + } + + @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 new file mode 100644 index 000000000000..096c5b4ec312 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java @@ -0,0 +1,767 @@ +/** + * 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.io.ByteArrayInputStream; +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; +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; +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.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.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; +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.GetServicePropertiesResult; +import com.microsoft.windowsazure.services.table.models.GetTableResult; +import com.microsoft.windowsazure.services.table.models.InsertEntityResult; +import com.microsoft.windowsazure.services.table.models.ListTablesOptions; +import com.microsoft.windowsazure.services.table.models.LitteralFilter; +import com.microsoft.windowsazure.services.table.models.Query; +import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions; +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; +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"; + private final HttpURLConnectionClient channel; + 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; + 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, HttpReaderWriter httpReaderWriter) { + + this.channel = channel; + this.url = url; + this.filter = filter; + this.dateMapper = new RFC1123DateConverter(); + this.iso8601DateConverter = iso8601DateConverter; + this.filters = new ServiceFilter[0]; + 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, + HttpReaderWriter httpReaderWriter, RFC1123DateConverter dateMapper, + ISO8601DateConverter iso8601DateConverter) { + + this.channel = channel; + this.filters = filters; + this.url = url; + this.filter = filter; + this.dateFactory = dateFactory; + this.atomReaderWriter = atomReaderWriter; + this.mimeReaderWriter = mimeReaderWriter; + this.httpReaderWriter = httpReaderWriter; + 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.url, this.filter, this.dateFactory, + this.atomReaderWriter, this.mimeReaderWriter, this.httpReaderWriter, 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 encodeODataURIValues(List values) { + List list = new ArrayList(); + for (String value : values) { + list.add(encodeODataURIValue(value)); + } + 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); + } + + private WebResource addOptionalQuery(WebResource webResource, Query query) { + if (query == null) + return webResource; + + if (query.getSelectFields() != null && query.getSelectFields().size() > 0) { + webResource = addOptionalQueryParam(webResource, "$select", + CommaStringBuilder.join(encodeODataURIValues(query.getSelectFields()))); + } + + 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.getOrderByFields() != null) { + webResource = addOptionalQueryParam(webResource, "$orderby", + CommaStringBuilder.join(encodeODataURIValues(query.getOrderByFields()))); + } + + return webResource; + } + + private String buildFilterExpression(Filter filter) { + StringBuilder sb = new StringBuilder(); + buildFilterExpression(filter, sb); + return sb.toString(); + } + + private void buildFilterExpression(Filter filter, StringBuilder sb) { + if (filter == null) + return; + + if (filter instanceof LitteralFilter) { + sb.append(((LitteralFilter) filter).getLitteral()); + } + else if (filter instanceof ConstantFilter) { + sb.append("'"); + sb.append(((ConstantFilter) filter).getValue()); + sb.append("'"); + } + else if (filter instanceof UnaryFilter) { + sb.append(((UnaryFilter) filter).getOperator()); + sb.append("("); + buildFilterExpression(((UnaryFilter) filter).getOperand(), sb); + sb.append(")"); + } + else if (filter instanceof BinaryFilter) { + sb.append("("); + buildFilterExpression(((BinaryFilter) filter).getLeft(), sb); + sb.append(" "); + sb.append(((BinaryFilter) filter).getOperator()); + sb.append(" "); + 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) { + 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.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()); + 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); + } + + @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.setTableEntry(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 + '{' + Filter filter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(options.getPrefix())), + Filter.le(Filter.litteral("TableName"), Filter.constant(options.getPrefix() + "{"))); + + QueryTablesOptions queryTableOptions = new QueryTablesOptions(); + queryTableOptions.setTimeout(options.getTimeout()); + queryTableOptions.setQuery(new Query().setFilter(filter)); + return queryTables(queryTableOptions); + } + + @Override + public QueryTablesResult queryTables() throws ServiceException { + return queryTables(new QueryTablesOptions()); + } + + @Override + 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); + + ClientResponse response = builder.get(ClientResponse.class); + ThrowIfError(response); + + QueryTablesResult result = new QueryTablesResult(); + result.setNextTableName(response.getHeaders().getFirst("x-ms-continuation-NextTableName")); + result.setTables(atomReaderWriter.parseTableEntries(response.getEntityInputStream())); + + return result; + } + + @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"); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + + builder.entity(atomReaderWriter.generateTableEntry(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); + } + + @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; + } + + @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 { + return putOrMergeEntityCore(table, entity, "PUT", true/*includeEtag*/, 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 putOrMergeEntityCore(table, entity, "MERGE", true/*includeEtag*/, options); + } + + @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); + } + + @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( + getEntityPath(table, entity.getPartitionKey(), entity.getRowKey())); + + WebResource.Builder builder = webResource.getRequestBuilder(); + builder = addTableRequestHeaders(builder); + if (includeEtag) { + builder = addIfMatchHeader(builder, entity.getEtag()); + } + + builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml"); + + ClientResponse response = builder.method(verb, ClientResponse.class); + ThrowIfError(response); + + UpdateEntityResult result = new UpdateEntityResult(); + result.setEtag(response.getHeaders().getFirst("ETag")); + + 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); + } + + @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 = addOptionalQueryParam(webResource, "NextPartitionKey", + encodeODataURIValue(options.getNextPartitionKey())); + webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(options.getNextRowKey())); + + 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; + } + + @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 = createBatchRequestBody(operations); + builder = builder.type(entity.getContentType()); + + ClientResponse response = builder.post(ClientResponse.class, entity); + ThrowIfError(response); + + BatchResult result = new BatchResult(); + result.setEntries(parseBatchResponse(response, operations)); + + return result; + } + + private MimeMultipart createBatchRequestBody(BatchOperations operations) { + List bodyPartContents = new ArrayList(); + int contentId = 1; + for (Operation operation : operations.getOperations()) { + + DataSource bodyPartContent = null; + if (operation instanceof InsertEntityOperation) { + InsertEntityOperation op = (InsertEntityOperation) operation; + 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) { + bodyPartContents.add(bodyPartContent); + } + } + + 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 parseBatchResponse(ClientResponse response, BatchOperations operations) { + List parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders() + .getFirst("Content-Type")); + + if (parts.size() != operations.getOperations().size()) { + throw new UniformInterfaceException(String.format( + "Batch response from server does not contain the correct amount " + + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations() + .size()), response); + } + + List result = new ArrayList(); + 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
e = headers.getAllHeaders(); + while (e.hasMoreElements()) { + Header header = e.nextElement(); + inBoundHeaders.putSingle(header.getName(), header.getValue()); + } + + 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); + } + } + + return result; + } + + 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/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/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java new file mode 100644 index 000000000000..f1df873ac4da --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java @@ -0,0 +1,208 @@ +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 InsertEntityOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addUpdateEntity(String table, Entity entity) { + this.operations.add(new UpdateEntityOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addMergeEntity(String table, Entity entity) { + this.operations.add(new MergeEntityOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addInsertOrReplaceEntity(String table, Entity entity) { + this.operations.add(new InsertOrReplaceEntityOperation().setTable(table).setEntity(entity)); + return this; + } + + public BatchOperations addInsertOrMergeEntity(String table, Entity entity) { + this.operations.add(new InsertOrMergeEntityOperation().setTable(table).setEntity(entity)); + return this; + } + + 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; + } + + public static abstract class Operation { + } + + public static class InsertEntityOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertEntityOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class UpdateEntityOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public UpdateEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public UpdateEntityOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class MergeEntityOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public MergeEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public MergeEntityOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class InsertOrReplaceEntityOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertOrReplaceEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertOrReplaceEntityOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class InsertOrMergeEntityOperation extends Operation { + private String table; + private Entity entity; + + public String getTable() { + return table; + } + + public InsertOrMergeEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public Entity getEntity() { + return entity; + } + + public InsertOrMergeEntityOperation setEntity(Entity entity) { + this.entity = entity; + return this; + } + } + + public static class DeleteEntityOperation extends Operation { + private String table; + private String partitionKey; + private String rowKey; + private String etag; + + public String getTable() { + return table; + } + + public DeleteEntityOperation setTable(String table) { + this.table = table; + return this; + } + + public String getPartitionKey() { + return partitionKey; + } + + public DeleteEntityOperation setPartitionKey(String partitionKey) { + this.partitionKey = partitionKey; + return this; + } + + public String getRowKey() { + return rowKey; + } + + 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/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..30a0b3beebd9 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java @@ -0,0 +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 entries = new ArrayList(); + + public List getEntries() { + return entries; + } + + public BatchResult setEntries(List 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/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/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java new file mode 100644 index 000000000000..9dd16b1a1daa --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class ConstantFilter extends Filter { + private Object value; + + public Object getValue() { + return 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/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/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..ce1d6dff3933 --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java @@ -0,0 +1,77 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.Date; +import java.util.HashMap; +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(); + } + + 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 getProperties() { + return properties; + } + + public Entity setProperties(Map 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/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java new file mode 100644 index 000000000000..f0a54f58b09b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java @@ -0,0 +1,51 @@ +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); + } + + 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/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/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/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java new file mode 100644 index 000000000000..de199b24f075 --- /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 tableEntry; + + public TableEntry getTableEntry() { + return tableEntry; + } + + 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/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/LitteralFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java new file mode 100644 index 000000000000..d67f47162c4d --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java @@ -0,0 +1,14 @@ +package com.microsoft.windowsazure.services.table.models; + +public class LitteralFilter extends Filter { + private String litteral; + + public String getLitteral() { + return 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/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/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/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java new file mode 100644 index 000000000000..1de1cd47426b --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java @@ -0,0 +1,40 @@ +package com.microsoft.windowsazure.services.table.models; + +public class QueryEntitiesOptions extends TableServiceOptions { + private Query query; + public String nextPartitionKey; + public String nextRowKey; + + @Override + public QueryEntitiesOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public Query getQuery() { + return 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/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/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..d402cd8527cc --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java @@ -0,0 +1,30 @@ +package com.microsoft.windowsazure.services.table.models; + +public class QueryTablesOptions extends TableServiceOptions { + private String nextTableName; + private Query query; + + @Override + public QueryTablesOptions setTimeout(Integer timeout) { + super.setTimeout(timeout); + return this; + } + + public Query getQuery() { + return 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 new file mode 100644 index 000000000000..dfd1c1b7f4aa --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java @@ -0,0 +1,24 @@ +package com.microsoft.windowsazure.services.table.models; + +import java.util.List; + +public class QueryTablesResult { + private String nextTableName; + private List tables; + + public String getNextTableName() { + return nextTableName; + } + + public void setNextTableName(String nextTableName) { + this.nextTableName = nextTableName; + } + + public List getTables() { + return tables; + } + + public void setTables(List tables) { + this.tables = tables; + } +} 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/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/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/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; + } +} diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java new file mode 100644 index 000000000000..42d4b7a870fc --- /dev/null +++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java @@ -0,0 +1,24 @@ +package com.microsoft.windowsazure.services.table.models; + +public class UnaryFilter extends Filter { + private String operator; + private Filter operand; + + public String getOperator() { + return operator; + } + + public UnaryFilter setOperator(String operator) { + this.operator = operator; + return this; + } + + public Filter getOperand() { + return operand; + } + + public UnaryFilter setOperand(Filter operand) { + this.operand = operand; + return this; + } +} 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/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/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); + } +} 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..c8143349741d --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java @@ -0,0 +1,913 @@ +/** + * 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.util.Date; +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.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.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; +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; +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 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; + private static String[] creatableTables; + private static String[] testTables; + + @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]; + 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]; + 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]; + //CREATABLE_TABLE_3 = creatableTables[2]; + + // Create all test containers and their content + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + deleteAllTables(service, testTables); + deleteAllTables(service, creatableTables); + 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 { + // 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)) { + 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 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)); + for (TableEntry item : list.getTables()) { + result.add(item.getName()); + } + return result; + } + + @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 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 + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + + // Act + QueryTablesResult result = service.queryTables(); + + // 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 + 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); + 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, entity); + + // Assert + assertNotNull(result); + assertNotNull(result.getEntity()); + + assertEquals("001", result.getEntity().getPartitionKey()); + assertEquals("insertEntityWorks", 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 updateEntityWorks() throws Exception { + System.out.println("updateEntityWorks()"); + + // 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, entity); + result.getEntity().setProperty("test4", EdmType.INT32, 5); + service.updateEntity(TEST_TABLE_2, result.getEntity()); + + // Assert + } + + @Test + public void insertOrReplaceEntityWorks() throws Exception { + System.out.println("insertOrReplaceEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + 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); + service.insertOrReplaceEntity(TEST_TABLE_2, entity); + + // Assert + } + + @Test + public void insertOrMergeEntityWorks() throws Exception { + System.out.println("insertOrMergeEntityWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + 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); + service.insertOrMergeEntity(TEST_TABLE_2, entity); + + // Assert + } + + @Test + public void mergeEntityWorks() throws Exception { + System.out.println("mergeEntityWorks()"); + + // 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, entity); + + 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()"); + + // 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, entity); + + 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); + 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, 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 queryEntitiesWorks() throws Exception { + System.out.println("queryEntitiesWorks()"); + + // Arrange + Configuration config = createConfiguration(); + TableContract service = TableService.create(config); + 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()); + + // 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("queryEntitiesWorks", 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); + } + + @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()); + } + + { + // 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()); + } + } + + @Test + 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"; + + // 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()); + + BatchResult result = service.batch(new BatchOperations().addInsertEntity(table, entity)); + + // Assert + assertNotNull(result); + assertEquals(1, result.getEntries().size()); + assertEquals(InsertEntity.class, result.getEntries().get(0).getClass()); + } + + @Test + 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 + 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); + } + } + + @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()); + } +} diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java new file mode 100644 index 000000000000..aa14a71f8597 --- /dev/null +++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java @@ -0,0 +1,80 @@ +/** + * Copyright 2011 Microsoft Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.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.IntegrationTestBase; +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(), new DefaultEdmValueConterter(new ISO8601DateConverter())); + String feed = "\r\n" + + "\r\n" + + " 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/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; + } + } +} 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