diff --git a/README.md b/README.md
index 6390ff1f65f7b..76a899e45b209 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
#Windows Azure SDK for Java
-This SDK allows you to build Windows Azure applications in Java that allow
+This SDK allows you to build Windows Azure applications in Java that allow
you to take advantage of Azure scalable cloud computing resources: table and blob
storage, messaging through Service Bus.
@@ -68,9 +68,9 @@ and uploading a file to it. For additional information on using the client libr
public class BlobSample {
public static final String storageConnectionString =
- "DefaultEndpointsProtocol=http;" +
- "AccountName=your_account_name;" +
- "AccountKey= your_account_name";
+ "DefaultEndpointsProtocol=http;" +
+ "AccountName=your_account_name;" +
+ "AccountKey= your_account_name";
public static void main(String[] args)
{
diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml
index c83d5dcc1a333..01567851ee74f 100644
--- a/microsoft-azure-api/pom.xml
+++ b/microsoft-azure-api/pom.xml
@@ -1,228 +1,228 @@
-
- 4.0.0
- com.microsoft.windowsazure
- microsoft-windowsazure-api
- 0.2.0
- jar
-
- Microsoft Windows Azure Client API
- API for Microsoft Windows Azure Clients
- https://github.com/WindowsAzure/azure-sdk-for-java
-
-
-
- The Apache Software License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
- repo
-
-
-
-
- scm:git:https://github.com/WindowsAzure/azure-sdk-for-java
- scm:git:git://github.com/WindowsAzure/azure-sdk-for-java.git
-
-
-
- UTF-8
-
-
-
-
-
- microsoft
- Microsoft
-
-
-
-
-
- com.sun.jersey
- jersey-client
- 1.10-b02
-
-
- javax.xml.bind
- jaxb-api
- 2.1
- provided
-
-
- junit
- junit
- 4.8
- test
-
-
- org.hamcrest
- hamcrest-all
- 1.1
- test
-
-
- org.mockito
- mockito-all
- test
- 1.9.0-rc1
-
-
- javax.inject
- javax.inject
- 1
-
-
- com.sun.jersey
- jersey-json
- 1.10-b02
-
-
- commons-logging
- commons-logging
- 1.1.1
-
-
- javax.mail
- mail
- 1.4
-
-
- org.apache.commons
- commons-lang3
- 3.1
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-help-plugin
- 2.1.1
-
-
- validate
-
- evaluate
-
-
- legal
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 2.3.2
-
-
- 1.6
-
-
-
- org.jvnet.jaxb2.maven2
- maven-jaxb2-plugin
- 0.8.0
-
-
- generate-sources
-
- generate
-
-
-
-
- true
-
-
- org.jvnet.jaxb2_commons
- jaxb2-basics
- 0.6.0
-
-
- org.jvnet.jaxb2_commons
- jaxb2-basics-annotate
- 0.6.0
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- 2.8
-
- *.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization
- /**
-
* 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.
-
*/]]>
-
-
-
-
- org.codehaus.mojo
- findbugs-maven-plugin
- 2.3.2
-
- true
- true
- true
-
-
-
-
- org.apache.maven.plugins
- maven-checkstyle-plugin
- 2.8
-
- src/config/checkstyle.xml
-
-
-
-
-
-
-
-
-
- org.eclipse.m2e
- lifecycle-mapping
- 1.0.0
-
-
-
-
-
- org.apache.maven.plugins
- maven-help-plugin
- [2.1.1,)
-
- evaluate
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ 4.0.0
+ com.microsoft.windowsazure
+ microsoft-windowsazure-api
+ 0.2.1
+ jar
+
+ Microsoft Windows Azure Client API
+ API for Microsoft Windows Azure Clients
+ https://github.com/WindowsAzure/azure-sdk-for-java
+
+
+
+ The Apache Software License, Version 2.0
+ http://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ scm:git:https://github.com/WindowsAzure/azure-sdk-for-java
+ scm:git:git://github.com/WindowsAzure/azure-sdk-for-java.git
+
+
+
+ UTF-8
+
+
+
+
+
+ microsoft
+ Microsoft
+
+
+
+
+
+ com.sun.jersey
+ jersey-client
+ 1.10-b02
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.1
+ provided
+
+
+ junit
+ junit
+ 4.8
+ test
+
+
+ org.hamcrest
+ hamcrest-all
+ 1.1
+ test
+
+
+ org.mockito
+ mockito-all
+ test
+ 1.9.0-rc1
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+ com.sun.jersey
+ jersey-json
+ 1.10-b02
+
+
+ commons-logging
+ commons-logging
+ 1.1.1
+
+
+ javax.mail
+ mail
+ 1.4
+
+
+ org.apache.commons
+ commons-lang3
+ 3.1
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-help-plugin
+ 2.1.1
+
+
+ validate
+
+ evaluate
+
+
+ legal
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 2.3.2
+
+
+ 1.6
+
+
+
+ org.jvnet.jaxb2.maven2
+ maven-jaxb2-plugin
+ 0.8.0
+
+
+ generate-sources
+
+ generate
+
+
+
+
+ true
+
+
+ org.jvnet.jaxb2_commons
+ jaxb2-basics
+ 0.6.0
+
+
+ org.jvnet.jaxb2_commons
+ jaxb2-basics-annotate
+ 0.6.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 2.8
+
+ *.implementation.*;*.utils.*;com.microsoft.schemas._2003._10.serialization
+ /**
+
* 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.
+
*/]]>
+
+
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+ 2.3.2
+
+ true
+ true
+ true
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 2.8
+
+ src/config/checkstyle.xml
+
+
+
+
+
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-help-plugin
+ [2.1.1,)
+
+ evaluate
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
index 197537a9db826..8d9d63bc8972d 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
@@ -2,20 +2,21 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob;
import com.microsoft.windowsazure.services.blob.implementation.BlobExceptionProcessor;
import com.microsoft.windowsazure.services.blob.implementation.BlobRestProxy;
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
import com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter;
import com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter;
import com.microsoft.windowsazure.services.core.Builder;
@@ -28,5 +29,6 @@ public void register(Builder.Registry registry) {
registry.add(BlobRestProxy.class);
registry.add(SharedKeyLiteFilter.class);
registry.add(SharedKeyFilter.class);
+ registry.add(ISO8601DateConverter.class);
}
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
index 3907ac20db407..861cd21f3672f 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob.implementation;
@@ -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;
@@ -122,6 +122,10 @@ private void ThrowIfError(ClientResponse r) {
PipelineHelpers.ThrowIfError(r);
}
+ private void ThrowIfNotSuccess(ClientResponse clientResponse) {
+ PipelineHelpers.ThrowIfNotSuccess(clientResponse);
+ }
+
private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) {
return PipelineHelpers.addOptionalQueryParam(webResource, key, value);
}
@@ -155,18 +159,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;
}
@@ -630,7 +634,7 @@ public GetBlobResult getBlob(String container, String blob, GetBlobOptions optio
builder = addOptionalAccessContitionHeader(builder, options.getAccessCondition());
ClientResponse response = builder.get(ClientResponse.class);
- ThrowIfError(response);
+ ThrowIfNotSuccess(response);
GetBlobPropertiesResult properties = getBlobPropertiesResultFromResponse(response);
GetBlobResult blobResult = new GetBlobResult();
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
index 39e67d931a384..f1ea12dbaa76f 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
@@ -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().shortFormat(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 209a345d26a0a..0000000000000
--- 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 0000000000000..34444b858a039
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.blob.implementation;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/*
+ * "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.SSS'Z'";
+ private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+ private static final String DATETIME_PATTERN_NO_S = "yyyy-MM-dd'T'HH:mm'Z'";
+ private static final String DATETIME_PATTERN_TO_DECIMAL = "yyyy-MM-dd'T'HH:mm:ss.";
+
+ public String format(Date date) {
+ DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return iso8601Format.format(date);
+ }
+
+ public String shortFormat(Date date) {
+ DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return iso8601Format.format(date);
+ }
+
+ public Date parse(String date) throws ParseException {
+ if (date == null)
+ return null;
+
+ int length = date.length();
+ if (length == 17) {
+ // [2012-01-04T23:21Z] length = 17
+ return parseDateFromString(date, DATETIME_PATTERN_NO_S);
+ }
+ else if (length == 20) {
+ // [2012-01-04T23:21:59Z] length = 20
+ return parseDateFromString(date, SHORT_DATETIME_PATTERN);
+ }
+ else if (length >= 22 && length <= 28) {
+ // [2012-01-04T23:21:59.1Z] length = 22
+ // [2012-01-04T23:21:59.1234567Z] length = 28
+ // Need to handle the milliseconds gently.
+
+ Date allExceptMilliseconds = parseDateFromString(date, DATETIME_PATTERN_TO_DECIMAL);
+ long timeWithSecondGranularity = allExceptMilliseconds.getTime();
+ // Decimal point is at 19
+ String secondDecimalString = date.substring(19, date.indexOf('Z'));
+ Float secondDecimal = Float.parseFloat(secondDecimalString);
+ int milliseconds = Math.round(secondDecimal * 1000);
+ long timeInMS = timeWithSecondGranularity + milliseconds;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(timeInMS);
+ return calendar.getTime();
+ }
+ else {
+ throw new IllegalArgumentException(String.format("Invalid Date String: %s", date));
+ }
+ }
+
+ private static Date parseDateFromString(final String value, final String pattern) throws ParseException {
+ DateFormat iso8601Format = new SimpleDateFormat(pattern, Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return iso8601Format.parse(value);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
index 6481932c7cde0..9072ead435645 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
@@ -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 0134195d5c3aa..37ff1321d5d96 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob.implementation;
@@ -59,6 +59,7 @@ public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
return this.getNext().handle(cr);
}
+ @Override
public void onBeforeStreamingEntity(ClientRequest clientRequest) {
// All headers should be known at this point, time to sign!
sign(clientRequest);
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java
new file mode 100644
index 0000000000000..3623028e5ef16
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java
@@ -0,0 +1,47 @@
+package com.microsoft.windowsazure.services.core.utils;
+
+import java.util.List;
+
+public class CommaStringBuilder {
+ private final StringBuilder sb = new StringBuilder();
+
+ public void add(String representation) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(representation);
+ }
+
+ public void addValue(boolean value, String representation) {
+ if (value) {
+ add(representation);
+ }
+ }
+
+ public static String join(List values) {
+ CommaStringBuilder sb = new CommaStringBuilder();
+
+ for (String value : values) {
+ sb.add(value);
+ }
+
+ return sb.toString();
+ }
+
+ public static String join(String... values) {
+ CommaStringBuilder sb = new CommaStringBuilder();
+
+ for (String value : values) {
+ sb.add(value);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (sb.length() == 0)
+ return null;
+ return sb.toString();
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
index a562b7c2122e7..234581b023fb8 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
@@ -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 0610eb8d7bc94..20d9046c5358c 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java
@@ -27,7 +27,7 @@
import javax.ws.rs.core.MultivaluedMap;
-import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers.EnumCommaStringBuilder;
+import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
@@ -269,7 +269,7 @@ private void setURLConnectionHeaders(MultivaluedMap headers, Htt
urlConnection.setRequestProperty(e.getKey(), ClientRequest.getHeaderValue(vs.get(0)));
}
else {
- EnumCommaStringBuilder sb = new EnumCommaStringBuilder();
+ CommaStringBuilder sb = new CommaStringBuilder();
for (Object v : e.getValue()) {
sb.add(ClientRequest.getHeaderValue(v));
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
index 95fce9e9dbeb2..21b95538f6e11 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
@@ -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;
@@ -27,32 +27,17 @@
import com.sun.jersey.api.client.WebResource.Builder;
public class PipelineHelpers {
- public static void ThrowIfError(ClientResponse r) {
- if (r.getStatus() >= 300) {
- 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 static void ThrowIfNotSuccess(ClientResponse clientResponse) {
+ int statusCode = clientResponse.getStatus();
- public void addValue(boolean value, String representation) {
- if (value) {
- add(representation);
- }
+ if ((statusCode < 200) || (statusCode >= 300)) {
+ throw new UniformInterfaceException(clientResponse);
}
+ }
- public String getValue() {
- if (sb.length() == 0)
- return null;
- return sb.toString();
+ public static void ThrowIfError(ClientResponse clientResponse) {
+ if (clientResponse.getStatus() >= 400) {
+ throw new UniformInterfaceException(clientResponse);
}
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java
index 4677d0431b818..5816a1bec264d 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/queue/implementation/QueueRestProxy.java
@@ -152,6 +152,9 @@ public void createQueue(String queue) throws ServiceException {
}
public void createQueue(String queue, CreateQueueOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue);
WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION);
@@ -165,6 +168,9 @@ public void deleteQueue(String queue) throws ServiceException {
}
public void deleteQueue(String queue, QueueServiceOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue);
WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION);
@@ -195,6 +201,9 @@ public GetQueueMetadataResult getQueueMetadata(String queue) throws ServiceExcep
}
public GetQueueMetadataResult getQueueMetadata(String queue, QueueServiceOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).queryParam("comp", "metadata");
Builder builder = webResource.header("x-ms-version", API_VERSION);
@@ -216,6 +225,9 @@ public void setQueueMetadata(String queue, HashMap metadata) thr
public void setQueueMetadata(String queue, HashMap metadata, QueueServiceOptions options)
throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).queryParam("comp", "metadata");
WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION);
@@ -229,6 +241,9 @@ public void createMessage(String queue, String messageText) throws ServiceExcept
}
public void createMessage(String queue, String messageText, CreateMessageOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages");
webResource = addOptionalQueryParam(webResource, "visibilitytimeout", options.getVisibilityTimeoutInSeconds());
webResource = addOptionalQueryParam(webResource, "messagettl", options.getTimeToLiveInSeconds());
@@ -249,6 +264,11 @@ public UpdateMessageResult updateMessage(String queue, String messageId, String
public UpdateMessageResult updateMessage(String queue, String messageId, String popReceipt, String messageText,
int visibilityTimeoutInSeconds, QueueServiceOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+ if (messageId == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages").path(messageId);
webResource = addOptionalQueryParam(webResource, "popreceipt", popReceipt);
webResource = addOptionalQueryParam(webResource, "visibilitytimeout", visibilityTimeoutInSeconds);
@@ -272,6 +292,9 @@ public ListMessagesResult listMessages(String queue) throws ServiceException {
}
public ListMessagesResult listMessages(String queue, ListMessagesOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages");
webResource = addOptionalQueryParam(webResource, "visibilitytimeout", options.getVisibilityTimeoutInSeconds());
webResource = addOptionalQueryParam(webResource, "numofmessages", options.getNumberOfMessages());
@@ -286,6 +309,9 @@ public PeekMessagesResult peekMessages(String queue) throws ServiceException {
}
public PeekMessagesResult peekMessages(String queue, PeekMessagesOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages").queryParam("peekonly", "true");
webResource = addOptionalQueryParam(webResource, "numofmessages", options.getNumberOfMessages());
@@ -300,6 +326,11 @@ public void deleteMessage(String queue, String messageId, String popReceipt) thr
public void deleteMessage(String queue, String messageId, String popReceipt, QueueServiceOptions options)
throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+ if (messageId == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages").path(messageId);
webResource = addOptionalQueryParam(webResource, "popreceipt", popReceipt);
@@ -313,6 +344,9 @@ public void clearMessages(String queue) throws ServiceException {
}
public void clearMessages(String queue, QueueServiceOptions options) throws ServiceException {
+ if (queue == null)
+ throw new NullPointerException();
+
WebResource webResource = getResource(options).path(queue).path("messages");
Builder builder = webResource.header("x-ms-version", API_VERSION);
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java
new file mode 100644
index 0000000000000..4a6fd5fb37fbc
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+public interface EdmValueConverter {
+ 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 0000000000000..ad0334ab03de4
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+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 0000000000000..33391fc9e711f
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+public class TableConfiguration {
+ public final static String ACCOUNT_NAME = "table.accountName";
+ public final static String ACCOUNT_KEY = "table.accountKey";
+ public final static String URI = "table.uri";
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java
new file mode 100644
index 0000000000000..2bb4e1a3ac6ae
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+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.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 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 0000000000000..c37f20da147f8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+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 0000000000000..ddff3f9aa8895
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java
@@ -0,0 +1,330 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.ByteArrayInputStream;
+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);
+ }
+ else {
+ writer.writeAttribute("m:null", "true");
+ }
+
+ 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();
+
+ // Use concatenation instead of StringBuilder as most text is just one element.
+ String serializedValue = "";
+ while (!xmlr.isEndElement()) {
+ serializedValue += xmlr.getText();
+ xmlr.next();
+ }
+
+ Object value = edmValueConverter.deserialize(edmType, serializedValue);
+
+ result.put(name, new Property().setEdmType(edmType).setValue(value));
+
+ 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 0000000000000..d35293670c1b8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.text.ParseException;
+import java.util.Date;
+import java.util.UUID;
+
+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;
+import com.sun.jersey.core.util.Base64;
+
+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 if (value instanceof byte[]) {
+ serializedValue = new String(Base64.encode((byte[]) 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);
+ }
+ else if (EdmType.BINARY.equals(edmType)) {
+ return Base64.decode(value);
+ }
+ else if (EdmType.GUID.equals(edmType)) {
+ return UUID.fromString(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 0000000000000..6202250942d4f
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.InputStream;
+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 0000000000000..347538853151a
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+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
+ @SuppressWarnings("unchecked")
+ 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 0000000000000..a15d80844155d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+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 0000000000000..30aa13ec6bfe8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+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 0000000000000..7e2e95bb88f9a
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.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().getRawPath();
+
+ 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 0000000000000..6f9990616a822
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import javax.inject.Named;
+
+import com.microsoft.windowsazure.services.table.TableConfiguration;
+
+public class SharedKeyLiteFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter {
+ public SharedKeyLiteFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName,
+ @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) {
+ super(accountName, accountKey);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java
new file mode 100644
index 0000000000000..29e8bd6dd8129
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java
@@ -0,0 +1,464 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import 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.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 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 0000000000000..7da2c6a1b4905
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java
@@ -0,0 +1,908 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Formatter;
+import java.util.List;
+import java.util.UUID;
+
+import javax.activation.DataSource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.mail.Header;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMultipart;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter;
+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.PropertyNameFilter;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryStringFilter;
+import com.microsoft.windowsazure.services.table.models.QueryTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.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;
+import com.sun.jersey.core.util.ReaderWriter;
+
+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='" + safeEncode(partitionKey) + "',RowKey='" + safeEncode(rowKey) + "')";
+ }
+
+ private String safeEncode(String input) {
+ String fixSingleQuotes = input.replace("'", "''");
+ try {
+ return URLEncoder.encode(fixSingleQuotes, "UTF-8").replace("+", "%20");
+ }
+ catch (UnsupportedEncodingException e) {
+ return fixSingleQuotes;
+ }
+ }
+
+ private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) {
+ return PipelineHelpers.addOptionalQueryParam(webResource, key, value);
+ }
+
+ private WebResource addOptionalQueryEntitiesOptions(WebResource webResource,
+ QueryEntitiesOptions queryEntitiesOptions) {
+ if (queryEntitiesOptions == null)
+ return webResource;
+
+ if (queryEntitiesOptions.getSelectFields() != null && queryEntitiesOptions.getSelectFields().size() > 0) {
+ webResource = addOptionalQueryParam(webResource, "$select",
+ CommaStringBuilder.join(encodeODataURIValues(queryEntitiesOptions.getSelectFields())));
+ }
+
+ if (queryEntitiesOptions.getTop() != null) {
+ webResource = addOptionalQueryParam(webResource, "$top", encodeODataURIValue(queryEntitiesOptions.getTop()
+ .toString()));
+ }
+
+ if (queryEntitiesOptions.getFilter() != null) {
+ webResource = addOptionalQueryParam(webResource, "$filter",
+ buildFilterExpression(queryEntitiesOptions.getFilter()));
+ }
+
+ if (queryEntitiesOptions.getOrderByFields() != null) {
+ webResource = addOptionalQueryParam(webResource, "$orderby",
+ CommaStringBuilder.join(encodeODataURIValues(queryEntitiesOptions.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 PropertyNameFilter) {
+ sb.append(((PropertyNameFilter) filter).getPropertyName());
+ }
+ else if (filter instanceof ConstantFilter) {
+ Object value = ((ConstantFilter) filter).getValue();
+ if (value == null) {
+ sb.append("null");
+ }
+ else if (value.getClass() == Long.class) {
+ sb.append(value);
+ sb.append("L");
+ }
+ else if (value.getClass() == Date.class) {
+ ISO8601DateConverter dateConverter = new ISO8601DateConverter();
+ sb.append("datetime'");
+ sb.append(dateConverter.format((Date) value));
+ sb.append("'");
+ }
+ else if (value.getClass() == UUID.class) {
+ sb.append("(guid'");
+ sb.append(value);
+ sb.append("')");
+ }
+ else if (value.getClass() == String.class) {
+ sb.append("'");
+ sb.append(((String) value).replace("'", "''"));
+ sb.append("'");
+ }
+ else if (value.getClass() == byte[].class) {
+ sb.append("X'");
+ byte[] byteArray = (byte[]) value;
+ Formatter formatter = new Formatter(sb);
+ for (byte b : byteArray) {
+ formatter.format("%02x", b);
+ }
+ sb.append("'");
+ }
+ else if (value.getClass() == Byte[].class) {
+ sb.append("X'");
+ Byte[] byteArray = (Byte[]) value;
+ Formatter formatter = new Formatter(sb);
+ for (Byte b : byteArray) {
+ formatter.format("%02x", b);
+ }
+ sb.append("'");
+ }
+ else {
+ sb.append(value);
+ }
+ }
+ 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 QueryStringFilter) {
+ sb.append(((QueryStringFilter) filter).getQueryString());
+ }
+ }
+
+ 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("/");
+ 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 {
+ if (serviceProperties == null)
+ throw new NullPointerException();
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 queryTables() throws ServiceException {
+ return queryTables(new QueryTablesOptions());
+ }
+
+ @Override
+ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException {
+ Filter queryFilter = options.getFilter();
+ String nextTableName = options.getNextTableName();
+ String prefix = options.getPrefix();
+
+ if (prefix != null) {
+ // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> upperBound is prefix + '{'
+ Filter prefixFilter = Filter.and(Filter.ge(Filter.propertyName("TableName"), Filter.constant(prefix)),
+ Filter.le(Filter.propertyName("TableName"), Filter.constant(prefix + "{")));
+
+ if (queryFilter == null) {
+ queryFilter = prefixFilter;
+ }
+ else {
+ queryFilter = Filter.and(queryFilter, prefixFilter);
+ }
+ }
+
+ WebResource webResource = getResource(options).path("Tables");
+ webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(queryFilter));
+ webResource = addOptionalQueryParam(webResource, "NextTableName", nextTableName);
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 mergeEntity(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 insertOrMergeEntity(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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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());
+ }
+ if (verb == "MERGE") {
+ builder = builder.header("X-HTTP-Method", "MERGE");
+ verb = "POST";
+ }
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ 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 {
+ if (table == null)
+ throw new NullPointerException();
+
+ if (options == null)
+ options = new QueryEntitiesOptions();
+
+ WebResource webResource = getResource(options).path(table);
+ webResource = addOptionalQueryEntitiesOptions(webResource, options);
+ 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();
+
+ try {
+ result.setEntries(parseBatchResponse(response, operations));
+ }
+ catch (IOException e) {
+ throw new ServiceException(e);
+ }
+
+ 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) throws IOException {
+ // Default stream cannot be reset, but it is needed by multiple parts of this method.
+ // Replace the default response stream with one that can be read multiple times.
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ InputStream inputStream = response.getEntityInputStream();
+ ReaderWriter.writeTo(inputStream, byteArrayOutputStream);
+ response.setEntityInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
+
+ List parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders()
+ .getFirst("Content-Type"));
+
+ if (parts.size() == 0 || parts.size() > operations.getOperations().size()) {
+ throw new UniformInterfaceException(String.format(
+ "Batch response from server does not contain the correct amount "
+ + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations()
+ .size()), response);
+ }
+
+ Entry[] entries = new Entry[operations.getOperations().size()];
+ for (int i = 0; i < parts.size(); i++) {
+ DataSource ds = parts.get(i);
+ Operation operation = operations.getOperations().get(i);
+
+ StatusLine status = httpReaderWriter.parseStatusLine(ds);
+ InternetHeaders headers = httpReaderWriter.parseHeaders(ds);
+ InputStream content = httpReaderWriter.parseEntity(ds);
+ ByteArrayOutputStream contentByteArrayOutputStream = new ByteArrayOutputStream();
+ ReaderWriter.writeTo(content, contentByteArrayOutputStream);
+ content = new ByteArrayInputStream(contentByteArrayOutputStream.toByteArray());
+
+ if (status.getStatus() >= 400) {
+ // Create dummy client response with status, headers and content
+ InBoundHeaders inBoundHeaders = new InBoundHeaders();
+
+ @SuppressWarnings("unchecked")
+ Enumeration 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);
+
+ // Parse the message to find which operation caused this error.
+ try {
+ XMLInputFactory xmlStreamFactory = XMLInputFactory.newFactory();
+ content.reset();
+ XMLStreamReader xmlStreamReader = xmlStreamFactory.createXMLStreamReader(content);
+
+ while (xmlStreamReader.hasNext()) {
+ xmlStreamReader.next();
+ if (xmlStreamReader.isStartElement() && "message".equals(xmlStreamReader.getLocalName())) {
+ xmlStreamReader.next();
+ // Process "message" elements only
+ String message = xmlStreamReader.getText();
+ int colonIndex = message.indexOf(':');
+ String errorOpId = message.substring(0, colonIndex);
+ int opId = Integer.parseInt(errorOpId);
+ entries[opId] = error;
+ break;
+ }
+ }
+ xmlStreamReader.close();
+ }
+ catch (XMLStreamException e1) {
+ throw new UniformInterfaceException(
+ "Batch response from server does not contain XML in the expected format", response);
+ }
+ }
+ else if (operation instanceof InsertEntityOperation) {
+ InsertEntity opResult = new InsertEntity().setEntity(atomReaderWriter.parseEntityEntry(content));
+ entries[i] = opResult;
+ }
+ else if ((operation instanceof UpdateEntityOperation) || (operation instanceof MergeEntityOperation)
+ || (operation instanceof InsertOrReplaceEntityOperation)
+ || (operation instanceof InsertOrMergeEntityOperation)) {
+ UpdateEntity opResult = new UpdateEntity().setEtag(headers.getHeader("ETag", null));
+ entries[i] = opResult;
+ }
+ else if (operation instanceof DeleteEntityOperation) {
+ DeleteEntity opResult = new DeleteEntity();
+ entries[i] = opResult;
+ }
+ }
+
+ List result = new ArrayList();
+ for (int i = 0; i < entries.length; i++) {
+ result.add(entries[i]);
+ }
+
+ 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 0000000000000..064becc8b8650
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.InputStream;
+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 0000000000000..5de01c01542a8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java
@@ -0,0 +1,222 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class 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 0000000000000..6ca4400068bb0
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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 0000000000000..7666da869e056
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class BinaryFilter extends Filter {
+ private final String operator;
+ private final Filter left;
+ private final Filter right;
+
+ public BinaryFilter(Filter left, String operator, Filter right) {
+ this.left = left;
+ this.operator = operator;
+ this.right = right;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public Filter getLeft() {
+ return left;
+ }
+
+ public Filter getRight() {
+ return right;
+ }
+
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java
new file mode 100644
index 0000000000000..aa3dd041be7cc
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class ConstantFilter extends Filter {
+ private final Object value;
+
+ public ConstantFilter(Object value) {
+ this.value = value;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java
new file mode 100644
index 0000000000000..4cce1e14a24a9
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class DeleteEntityOptions extends TableServiceOptions {
+ private String etag;
+
+ 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 0000000000000..0f0585f2e22a9
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class EdmType {
+ public static final String DATETIME = "Edm.DateTime";
+ public static final String BINARY = "Edm.Binary";
+ public static final String BOOLEAN = "Edm.Boolean";
+ public static final String DOUBLE = "Edm.Double";
+ public static final String GUID = "Edm.Guid";
+ public static final String INT32 = "Edm.Int32";
+ public static final String INT64 = "Edm.Int64";
+ public static final String STRING = "Edm.String";
+}
\ No newline at end of file
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java
new file mode 100644
index 0000000000000..f74bda6d7003f
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.Date;
+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 0000000000000..1e410d2adfb46
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class Filter {
+ public static UnaryFilter not(Filter operand) {
+ return new UnaryFilter("not", operand);
+ }
+
+ public static BinaryFilter and(Filter left, Filter right) {
+ return new BinaryFilter(left, "and", right);
+ }
+
+ public static BinaryFilter or(Filter left, Filter right) {
+ return new BinaryFilter(left, "or", right);
+ }
+
+ public static BinaryFilter eq(Filter left, Filter right) {
+ return new BinaryFilter(left, "eq", right);
+ }
+
+ public static BinaryFilter ne(Filter left, Filter right) {
+ return new BinaryFilter(left, "ne", right);
+ }
+
+ public static BinaryFilter ge(Filter left, Filter right) {
+ return new BinaryFilter(left, "ge", right);
+ }
+
+ public static BinaryFilter gt(Filter left, Filter right) {
+ return new BinaryFilter(left, "gt", right);
+ }
+
+ public static BinaryFilter lt(Filter left, Filter right) {
+ return new BinaryFilter(left, "lt", right);
+ }
+
+ public static BinaryFilter le(Filter left, Filter right) {
+ return new BinaryFilter(left, "le", right);
+ }
+
+ public static ConstantFilter constant(Object value) {
+ return new ConstantFilter(value);
+ }
+
+ public static PropertyNameFilter propertyName(String value) {
+ return new PropertyNameFilter(value);
+ }
+
+ public static QueryStringFilter queryString(String value) {
+ return new QueryStringFilter(value);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java
new file mode 100644
index 0000000000000..336c436da06c8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class GetEntityResult {
+ 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 0000000000000..e56a2c8f9368d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class 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 0000000000000..0e23a0d752c20
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class GetTableResult {
+ 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 0000000000000..eca0203954967
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class InsertEntityResult {
+ private Entity entity;
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public void setEntity(Entity entity) {
+ this.entity = entity;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java
new file mode 100644
index 0000000000000..197584f920e6d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class Property {
+ 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/PropertyNameFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java
new file mode 100644
index 0000000000000..49cb129d86e03
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/PropertyNameFilter.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class PropertyNameFilter extends Filter {
+ private final String propertyName;
+
+ public PropertyNameFilter(String propertyName) {
+ this.propertyName = propertyName;
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java
new file mode 100644
index 0000000000000..e5d7a7c511513
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QueryEntitiesOptions extends TableServiceOptions {
+
+ private List selectFields = new ArrayList();
+ private String from;
+ private Filter filter;
+ private List orderByFields = new ArrayList();
+ private Integer top;
+
+ public String nextPartitionKey;
+ public String nextRowKey;
+
+ 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;
+ }
+
+ public List getSelectFields() {
+ return selectFields;
+ }
+
+ public QueryEntitiesOptions setSelectFields(List selectFields) {
+ this.selectFields = selectFields;
+ return this;
+ }
+
+ public QueryEntitiesOptions addSelectField(String selectField) {
+ this.selectFields.add(selectField);
+ return this;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public QueryEntitiesOptions setFrom(String from) {
+ this.from = from;
+ return this;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public QueryEntitiesOptions setFilter(Filter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public List getOrderByFields() {
+ return orderByFields;
+ }
+
+ public QueryEntitiesOptions setOrderByFields(List orderByFields) {
+ this.orderByFields = orderByFields;
+ return this;
+ }
+
+ public QueryEntitiesOptions addOrderByField(String orderByField) {
+ this.orderByFields.add(orderByField);
+ return this;
+ }
+
+ public Integer getTop() {
+ return top;
+ }
+
+ public QueryEntitiesOptions setTop(Integer top) {
+ this.top = top;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java
new file mode 100644
index 0000000000000..9756661eef96b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class 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/QueryStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java
new file mode 100644
index 0000000000000..77341e2477c57
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryStringFilter.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class QueryStringFilter extends Filter {
+ private final String queryString;
+
+ public QueryStringFilter(String queryString) {
+ this.queryString = queryString;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java
new file mode 100644
index 0000000000000..d72c580a402f4
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class QueryTablesOptions extends TableServiceOptions {
+ private Filter filter;
+ private String nextTableName;
+ private String prefix;
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public QueryTablesOptions setFilter(Filter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public String getNextTableName() {
+ return nextTableName;
+ }
+
+ public QueryTablesOptions setNextTableName(String nextTableName) {
+ this.nextTableName = nextTableName;
+ return this;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public QueryTablesOptions setPrefix(String prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java
new file mode 100644
index 0000000000000..9c6158382dba3
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.List;
+
+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/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java
new file mode 100644
index 0000000000000..01a18543864b1
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import 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 0000000000000..2e520bc36db17
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class TableEntry {
+ 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 0000000000000..c4a733a6e3a6b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class TableServiceOptions {
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java
new file mode 100644
index 0000000000000..99aa90409830b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class UnaryFilter extends Filter {
+ private final String operator;
+ private final Filter operand;
+
+ public UnaryFilter(String operator, Filter operand) {
+ this.operator = operator;
+ this.operand = operand;
+ }
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public Filter getOperand() {
+ return operand;
+ }
+
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java
new file mode 100644
index 0000000000000..dc18aa98996ec
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2012 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class UpdateEntityResult {
+ 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 cc7087393981f..07fdb1c4e7b67 100644
--- a/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports
+++ b/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports
@@ -1,5 +1,6 @@
com.microsoft.windowsazure.services.blob.Exports
com.microsoft.windowsazure.services.queue.Exports
+com.microsoft.windowsazure.services.table.Exports
com.microsoft.windowsazure.services.serviceBus.Exports
com.microsoft.windowsazure.services.serviceBus.implementation.Exports
com.microsoft.windowsazure.services.core.utils.Exports
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java
new file mode 100644
index 0000000000000..c69d18d8f1ebb
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java
@@ -0,0 +1,123 @@
+/**
+ * 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.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+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);
+ String value2 = converter.format(result);
+
+ // Assert
+ assertNotNull(result);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(result);
+ calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+ assertEquals("Year", 2012, calendar.get(Calendar.YEAR));
+ assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1);
+ assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH));
+ assertEquals("Hour", 0, calendar.get(Calendar.HOUR));
+ assertEquals("Minute", 35, calendar.get(Calendar.MINUTE));
+ assertEquals("Second", 58, calendar.get(Calendar.SECOND));
+ assertEquals("Millisecond", 0, calendar.get(Calendar.MILLISECOND));
+
+ assertEquals("2012-01-12T00:35:58.000Z", value2);
+ }
+
+ @Test
+ public void longFormatWorks() throws Exception {
+ // Arrange
+ ISO8601DateConverter converter = new ISO8601DateConverter();
+ String value = "2012-01-12T00:35:58.1234567Z";
+
+ // Act
+ Date result = converter.parse(value);
+ String value2 = converter.format(result);
+
+ // Assert
+ assertNotNull(result);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(result);
+ calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+ assertEquals("Year", 2012, calendar.get(Calendar.YEAR));
+ assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1);
+ assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH));
+ assertEquals("Hour", 0, calendar.get(Calendar.HOUR));
+ assertEquals("Minute", 35, calendar.get(Calendar.MINUTE));
+ assertEquals("Second", 58, calendar.get(Calendar.SECOND));
+ assertEquals("Millisecond", 123, calendar.get(Calendar.MILLISECOND));
+
+ assertEquals("2012-01-12T00:35:58.123Z", value2);
+ }
+
+ @Test
+ public void mixedFormatWorks() throws Exception {
+ // Arrange
+ ISO8601DateConverter converter = new ISO8601DateConverter();
+ String value = "2012-01-12T00:35:58.12Z";
+
+ // Act
+ Date result = converter.parse(value);
+ String value2 = converter.format(result);
+
+ // Assert
+ assertNotNull(result);
+
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(result);
+ calendar.setTimeZone(TimeZone.getTimeZone("GMT"));
+ assertEquals("Year", 2012, calendar.get(Calendar.YEAR));
+ assertEquals("Month", 1, calendar.get(Calendar.MONTH) + 1);
+ assertEquals("Day", 12, calendar.get(Calendar.DAY_OF_MONTH));
+ assertEquals("Hour", 0, calendar.get(Calendar.HOUR));
+ assertEquals("Minute", 35, calendar.get(Calendar.MINUTE));
+ assertEquals("Second", 58, calendar.get(Calendar.SECOND));
+ assertEquals("Millisecond", 120, calendar.get(Calendar.MILLISECOND));
+
+ assertEquals("2012-01-12T00:35:58.120Z", value2);
+ }
+
+ @Test
+ public void shortFormatRoundTrips() throws Exception {
+ // Arrange
+ ISO8601DateConverter converter = new ISO8601DateConverter();
+ String value = "2012-01-12T00:35:58Z";
+
+ // Act
+ Date result = converter.parse(value);
+ String value2 = converter.shortFormat(result);
+ String value3 = converter.format(result);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(value, value2);
+ assertEquals("2012-01-12T00:35:58.000Z", value3);
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java
new file mode 100644
index 0000000000000..8b7b18382d59d
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import com.microsoft.windowsazure.services.core.Configuration;
+
+public abstract class IntegrationTestBase {
+ protected static Configuration createConfiguration() {
+ Configuration config = Configuration.getInstance();
+ overrideWithEnv(config, TableConfiguration.ACCOUNT_NAME);
+ overrideWithEnv(config, TableConfiguration.ACCOUNT_KEY);
+ overrideWithEnv(config, TableConfiguration.URI);
+ return config;
+ }
+
+ private static void overrideWithEnv(Configuration config, String key) {
+ String value = System.getenv(key);
+ if (value == null)
+ return;
+
+ config.setProperty(key, value);
+ }
+
+ protected static boolean isRunningWithEmulator(Configuration config) {
+ String accountName = "devstoreaccount1";
+ String accountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+
+ return accountName.equals(config.getProperty(TableConfiguration.ACCOUNT_NAME))
+ && accountKey.equals(config.getProperty(TableConfiguration.ACCOUNT_KEY));
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java
new file mode 100644
index 0000000000000..07b25300bc20b
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java
@@ -0,0 +1,1088 @@
+/**
+ * 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 java.util.UUID;
+
+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.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.ServiceProperties;
+import com.microsoft.windowsazure.services.table.models.TableEntry;
+
+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 = queryTables(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 = queryTables(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 queryTables(TableContract service, String prefix) throws Exception {
+ HashSet result = new HashSet();
+ QueryTablesResult list = service.queryTables(new QueryTablesOptions().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 queryTablesWithPrefixWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ QueryTablesResult result = service.queryTables(new QueryTablesOptions().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);
+ byte[] binaryData = new byte[] { 1, 2, 3, 4 };
+ UUID uuid = UUID.randomUUID();
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("insertEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData)
+ .setProperty("test7", EdmType.GUID, uuid);
+
+ // 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);
+
+ assertNotNull(result.getEntity().getProperty("test6"));
+ assertTrue(result.getEntity().getProperty("test6").getValue() instanceof byte[]);
+ byte[] returnedBinaryData = (byte[]) result.getEntity().getProperty("test6").getValue();
+ assertEquals(binaryData.length, returnedBinaryData.length);
+ for (int i = 0; i < binaryData.length; i++) {
+ assertEquals(binaryData[i], returnedBinaryData[i]);
+ }
+
+ assertNotNull(result.getEntity().getProperty("test7"));
+ assertTrue(result.getEntity().getProperty("test7").getValue() instanceof UUID);
+ assertEquals(uuid.toString(), result.getEntity().getProperty("test7").getValue().toString());
+ }
+
+ @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 deleteEntityTroublesomeKeyWorks() throws Exception {
+ System.out.println("deleteEntityTroublesomeKeyWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity1 = new Entity().setPartitionKey("001").setRowKey("key with spaces");
+ Entity entity2 = new Entity().setPartitionKey("001").setRowKey("key'with'quotes");
+ Entity entity3 = new Entity().setPartitionKey("001").setRowKey("keyWithUnicode \uB2E4");
+ Entity entity4 = new Entity().setPartitionKey("001").setRowKey("key 'with'' \uB2E4");
+
+ // Act
+ InsertEntityResult result1 = service.insertEntity(TEST_TABLE_2, entity1);
+ InsertEntityResult result2 = service.insertEntity(TEST_TABLE_2, entity2);
+ InsertEntityResult result3 = service.insertEntity(TEST_TABLE_2, entity3);
+ InsertEntityResult result4 = service.insertEntity(TEST_TABLE_2, entity4);
+
+ service.deleteEntity(TEST_TABLE_2, result1.getEntity().getPartitionKey(), result1.getEntity().getRowKey());
+ service.deleteEntity(TEST_TABLE_2, result2.getEntity().getPartitionKey(), result2.getEntity().getRowKey());
+ service.deleteEntity(TEST_TABLE_2, result3.getEntity().getPartitionKey(), result3.getEntity().getRowKey());
+ service.deleteEntity(TEST_TABLE_2, result4.getEntity().getPartitionKey(), result4.getEntity().getRowKey());
+
+ // Assert
+ try {
+ service.getEntity(TEST_TABLE_2, result1.getEntity().getPartitionKey(), result1.getEntity().getRowKey());
+ assertFalse("Expect an exception when getting an entity that does not exist", true);
+ }
+ catch (ServiceException e) {
+ assertEquals("expect getHttpStatusCode", 404, e.getHttpStatusCode());
+
+ }
+
+ QueryEntitiesResult assertResult2 = service.queryEntities(
+ TEST_TABLE_2,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("RowKey"),
+ Filter.constant("key'with'quotes"))));
+
+ assertEquals(0, assertResult2.getEntities().size());
+
+ QueryEntitiesResult assertResult3 = service.queryEntities(TEST_TABLE_2);
+ for (Entity entity : assertResult3.getEntities()) {
+ assertFalse("Entity3 should be removed from the table", entity3.getRowKey().equals(entity.getRowKey()));
+ assertFalse("Entity4 should be removed from the table", entity4.getRowKey().equals(entity.getRowKey()));
+ }
+ }
+
+ @Test
+ 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);
+ byte[] binaryData = new byte[] { 1, 2, 3, 4 };
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("getEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date()).setProperty("test6", EdmType.BINARY, binaryData);
+
+ // 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);
+
+ assertNotNull(result.getEntity().getProperty("test6"));
+ assertTrue(result.getEntity().getProperty("test6").getValue() instanceof byte[]);
+ byte[] returnedBinaryData = (byte[]) result.getEntity().getProperty("test6").getValue();
+ assertEquals(binaryData.length, returnedBinaryData.length);
+ for (int i = 0; i < binaryData.length; i++) {
+ assertEquals(binaryData[i], returnedBinaryData[i]);
+ }
+ }
+
+ @Test
+ 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;
+ Entity[] entities = new Entity[numberOfEntries];
+ for (int i = 0; i < numberOfEntries; i++) {
+ entities[i] = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithFilterWorks-" + i)
+ .setProperty("test", EdmType.BOOLEAN, (i % 2 == 0))
+ .setProperty("test2", EdmType.STRING, "'value'" + i).setProperty("test3", EdmType.INT32, i)
+ .setProperty("test4", EdmType.INT64, 12345678901L + i)
+ .setProperty("test5", EdmType.DATETIME, new Date(i * 1000))
+ .setProperty("test6", EdmType.GUID, UUID.randomUUID());
+
+ service.insertEntity(table, entities[i]);
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(
+ table,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("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().setFilter(Filter
+ .queryString("RowKey eq '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().setFilter(Filter.eq(Filter.propertyName("test"),
+ Filter.constant(true))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(3, result.getEntities().size());
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(
+ table,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test2"),
+ Filter.constant("'value'3"))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntities().size());
+ assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey());
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(
+ table,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test4"),
+ Filter.constant(12345678903L))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntities().size());
+ assertEquals("queryEntitiesWithFilterWorks-2", result.getEntities().get(0).getRowKey());
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(
+ table,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test5"),
+ Filter.constant(new Date(3000)))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntities().size());
+ assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey());
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(
+ table,
+ new QueryEntitiesOptions().setFilter(Filter.eq(Filter.propertyName("test6"),
+ Filter.constant(entities[3].getPropertyValue("test6")))));
+
+ // 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());
+ }
+
+ @Test
+ public void batchNegativeWorks() throws Exception {
+ System.out.println("batchNegativeWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_8;
+ String partitionKey = "001";
+
+ // Insert an entity the modify it outside of the batch
+ Entity entity1 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks1")
+ .setProperty("test", EdmType.INT32, 1);
+ Entity entity2 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks2")
+ .setProperty("test", EdmType.INT32, 2);
+ Entity entity3 = new Entity().setPartitionKey(partitionKey).setRowKey("batchNegativeWorks3")
+ .setProperty("test", EdmType.INT32, 3);
+
+ entity1 = service.insertEntity(table, entity1).getEntity();
+ entity2 = service.insertEntity(table, entity2).getEntity();
+ entity2.setProperty("test", EdmType.INT32, -2);
+ service.updateEntity(table, entity2);
+
+ // Act
+ BatchOperations batchOperations = new BatchOperations();
+
+ // The entity1 still has the original etag from the first submit,
+ // so this update should fail, because another update was already made.
+ entity1.setProperty("test", EdmType.INT32, 3);
+ batchOperations.addDeleteEntity(table, entity1.getPartitionKey(), entity1.getRowKey(), entity1.getEtag());
+ batchOperations.addUpdateEntity(table, entity2);
+ batchOperations.addInsertEntity(table, entity3);
+
+ BatchResult result = service.batch(batchOperations);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(batchOperations.getOperations().size(), result.getEntries().size());
+ assertNull("First result should be null", result.getEntries().get(0));
+ assertNotNull("Second result should not be null", result.getEntries().get(1));
+ assertEquals("Second result type", com.microsoft.windowsazure.services.table.models.BatchResult.Error.class,
+ result.getEntries().get(1).getClass());
+ com.microsoft.windowsazure.services.table.models.BatchResult.Error error = (com.microsoft.windowsazure.services.table.models.BatchResult.Error) result
+ .getEntries().get(1);
+ assertEquals("Second result status code", 412, error.getError().getHttpStatusCode());
+ assertNull("Third result should be null", result.getEntries().get(2));
+ }
+}
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 0000000000000..aa14a71f8597c
--- /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 0000000000000..c01fdeaebc3cf
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java
@@ -0,0 +1,336 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.MultipartDataSource;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.junit.Test;
+
+import com.microsoft.windowsazure.services.table.IntegrationTestBase;
+
+public class MimeMultipartTests extends IntegrationTestBase {
+ @Test
+ public void parseMimeWorks() throws Exception {
+ //@formatter:off
+ String s = "--batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb \r\n"
+ + "Content-Type: multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 201 Created\r\n"
+ + "Content-ID: 1\r\n"
+ + "Content-Type: application/atom+xml;charset=utf-8\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n"
+ + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n"
+ + " \r\n"
+ + " 2009-04-30T20:44:09Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Channel_19\r\n"
+ + " 1\r\n"
+ + " 2009-04-30T20:44:09.5789464Z\r\n"
+ + " .Net...\r\n"
+ + " 9\r\n"
+ + " \r\n"
+ + " \r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 201 Created\r\n"
+ + "Content-ID: 2\r\n"
+ + "Content-Type: application/atom+xml;charset=utf-8\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n"
+ + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n"
+ + " \r\n"
+ + " 2009-04-30T20:44:09Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Channel_19\r\n"
+ + " 2\r\n"
+ + " 2009-04-30T20:44:09.5789464Z\r\n"
+ + " Azure...\r\n"
+ + " 9\r\n"
+ + " \r\n"
+ + " \r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 204 No Content\r\n"
+ + "Content-ID: 3\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A10.0019041Z'\"\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 204 No Content\r\n"
+ + "Content-ID: 4\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977--\r\n"
+ + "--batchresponse_4c637ba4-b2f8-40f8-8856-c2d10d163a83--\r\n";
+ //@formatter:on
+
+ DataSource ds = new ByteArrayDataSource(s,
+ "multipart/mixed; boundary=batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb");
+ MimeMultipart m = new MimeMultipart(ds);
+
+ assertEquals(1, m.getCount());
+ assertTrue(m.getBodyPart(0) instanceof MimeBodyPart);
+
+ MimeBodyPart part = (MimeBodyPart) m.getBodyPart(0);
+ String contentType = part.getHeader("Content-Type", ":");
+ assertEquals("multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977", contentType);
+
+ DataSource ds2 = new ByteArrayDataSource(part.getInputStream(), contentType);
+ MimeMultipart m2 = new MimeMultipart(ds2);
+
+ assertEquals(4, m2.getCount());
+ }
+
+ @Test
+ public void buildMimeWorks() throws Exception {
+ //@formatter:off
+ String changeset1 = "POST http://myaccount.tables.core.windows.net/Blogs HTTP/1.1\r\n" +
+ "Content-ID: 1\r\n" +
+ "Content-Type: application/atom+xml;type=entry\r\n" +
+ "Content-Length: ###\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " \r\n" +
+ " 2009-04-30T20:45:13.7155321Z\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " Channel_19\r\n" +
+ " 1\r\n" +
+ " 0001-01-01T00:00:00\r\n" +
+ " 9\r\n" +
+ " .NET...\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "";
+ //@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" +
+ "\r\n" +
+ "\r\n" +
+ " \r\n" +
+ " 2009-04-30T20:45:13.7155321Z\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " Channel_19\r\n" +
+ " 1\r\n" +
+ " 0001-01-01T00:00:00\r\n" +
+ " 9\r\n" +
+ " .NET...\r\n" +
+ " \r\n" +
+ " \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" +
+ "\r\n" +
+ "\r\n" +
+ " \r\n" +
+ " 2009-04-30T20:45:13.7155321Z\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " \r\n" +
+ " Channel_19\r\n" +
+ " 1\r\n" +
+ " 0001-01-01T00:00:00\r\n" +
+ " 9\r\n" +
+ " .NET...\r\n" +
+ " \r\n" +
+ " \r\n" +
+ "\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 e7bf06b034de2..7739acd3e7333 100644
--- a/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties
+++ b/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties
@@ -7,4 +7,7 @@ blob.accountKey=%BLOB_ACCOUNTKEY%
blob.uri=http://%BLOB_ACCOUNTNAME%.blob.core.windows.net
queue.accountName=%QUEUE_ACCOUNTNAME%
queue.accountKey=%QUEUE_ACCOUNTKEY%
-queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net
\ No newline at end of file
+queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net
+table.accountName=%TABLE_ACCOUNTNAME%
+table.accountKey=%TABLE_ACCOUNTKEY%
+table.uri=http://%TABLE_ACCOUNTNAME%.table.core.windows.net
\ No newline at end of file