diff --git a/microsoft-azure-api/pom.xml b/microsoft-azure-api/pom.xml
index 5da3a62a9e6c..36651b165c7c 100644
--- a/microsoft-azure-api/pom.xml
+++ b/microsoft-azure-api/pom.xml
@@ -80,6 +80,11 @@
commons-logging
1.1.1
+
+ javax.mail
+ mail
+ 1.4
+
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
index 197537a9db82..8d9d63bc8972 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/Exports.java
@@ -2,20 +2,21 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob;
import com.microsoft.windowsazure.services.blob.implementation.BlobExceptionProcessor;
import com.microsoft.windowsazure.services.blob.implementation.BlobRestProxy;
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
import com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter;
import com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter;
import com.microsoft.windowsazure.services.core.Builder;
@@ -28,5 +29,6 @@ public void register(Builder.Registry registry) {
registry.add(BlobRestProxy.class);
registry.add(SharedKeyLiteFilter.class);
registry.add(SharedKeyFilter.class);
+ registry.add(ISO8601DateConverter.class);
}
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
index 3907ac20db40..5bba95ef4599 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/BlobRestProxy.java
@@ -69,10 +69,10 @@
import com.microsoft.windowsazure.services.blob.models.SetContainerMetadataOptions;
import com.microsoft.windowsazure.services.core.ServiceException;
import com.microsoft.windowsazure.services.core.ServiceFilter;
+import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder;
import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter;
import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient;
import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers;
-import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers.EnumCommaStringBuilder;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
@@ -155,18 +155,18 @@ private HashMap getMetadataFromHeaders(ClientResponse response)
}
private WebResource addOptionalBlobListingIncludeQueryParam(ListBlobsOptions options, WebResource webResource) {
- EnumCommaStringBuilder sb = new EnumCommaStringBuilder();
+ CommaStringBuilder sb = new CommaStringBuilder();
sb.addValue(options.isIncludeSnapshots(), "snapshots");
sb.addValue(options.isIncludeUncommittedBlobs(), "uncommittedblobs");
sb.addValue(options.isIncludeMetadata(), "metadata");
- webResource = addOptionalQueryParam(webResource, "include", sb.getValue());
+ webResource = addOptionalQueryParam(webResource, "include", sb.toString());
return webResource;
}
private WebResource addOptionalContainerIncludeQueryParam(ListContainersOptions options, WebResource webResource) {
- EnumCommaStringBuilder sb = new EnumCommaStringBuilder();
+ CommaStringBuilder sb = new CommaStringBuilder();
sb.addValue(options.isIncludeMetadata(), "metadata");
- webResource = addOptionalQueryParam(webResource, "include", sb.getValue());
+ webResource = addOptionalQueryParam(webResource, "include", sb.toString());
return webResource;
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
index 39e67d931a38..af860e50936d 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateAdapter.java
@@ -25,11 +25,11 @@ public class ContainerACLDateAdapter extends XmlAdapter {
@Override
public Date unmarshal(String arg0) throws Exception {
- return new ContainerACLDateConverter().parse(arg0);
+ return new ISO8601DateConverter().parse(arg0);
}
@Override
public String marshal(Date arg0) throws Exception {
- return new ContainerACLDateConverter().format(arg0);
+ return new ISO8601DateConverter().format(arg0);
}
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java
deleted file mode 100644
index 209a345d26a0..000000000000
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ContainerACLDateConverter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright 2011 Microsoft Corporation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.microsoft.windowsazure.services.blob.implementation;
-
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-/*
- * "not quite" ISO 8601 date time conversion routines
- */
-public class ContainerACLDateConverter {
- // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible
- private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'";
-
- public String format(Date date) {
- return getFormat().format(date);
- }
-
- public Date parse(String date) throws ParseException {
- return getFormat().parse(date);
- }
-
- public Date parseNoThrow(String date) {
- try {
- return parse(date);
- }
- catch (ParseException e) {
- return null;
- }
- }
-
- private DateFormat getFormat() {
- DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US);
- iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
- return iso8601Format;
- }
-}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java
new file mode 100644
index 000000000000..e796bf00794c
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverter.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.blob.implementation;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/*
+ * "not quite" ISO 8601 date time conversion routines
+ */
+public class ISO8601DateConverter {
+ // Note: because of the trailing "0000000", this is not quite ISO 8601 compatible
+ private static final String DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'";
+ private static final String SHORT_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
+
+ public String format(Date date) {
+ return getFormat().format(date);
+ }
+
+ public Date parse(String date) throws ParseException {
+ if (date == null)
+ return null;
+
+ // Sometimes, the date comes back without the ".SSSSSSS" part (presumably when the decimal value
+ // of the date is "0". Use the short format in that case.
+ if (date.indexOf('.') < 0)
+ return getShortFormat().parse(date);
+ else
+ return getFormat().parse(date);
+ }
+
+ private DateFormat getFormat() {
+ DateFormat iso8601Format = new SimpleDateFormat(DATETIME_PATTERN, Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return iso8601Format;
+ }
+
+ private DateFormat getShortFormat() {
+ DateFormat iso8601Format = new SimpleDateFormat(SHORT_DATETIME_PATTERN, Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return iso8601Format;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
index 6481932c7cde..9072ead43564 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyFilter.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob.implementation;
@@ -44,6 +44,18 @@ public SharedKeyFilter(@Named(BlobConfiguration.ACCOUNT_NAME) String accountName
this.signer = new HmacSHA256Sign(accountKey);
}
+ protected String getHeader(ClientRequest cr, String headerKey) {
+ return SharedKeyUtils.getHeader(cr, headerKey);
+ }
+
+ protected HmacSHA256Sign getSigner() {
+ return signer;
+ }
+
+ protected String getAccountName() {
+ return accountName;
+ }
+
@Override
public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
// Only sign if no other filter is handling authorization
@@ -60,6 +72,7 @@ public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
return this.getNext().handle(cr);
}
+ @Override
public void onBeforeStreamingEntity(ClientRequest clientRequest) {
// All headers should be known at this point, time to sign!
sign(clientRequest);
@@ -105,11 +118,11 @@ public void sign(ClientRequest cr) {
cr.getHeaders().putSingle("Authorization", "SharedKey " + this.accountName + ":" + signature);
}
- private void addOptionalDateHeader(ClientRequest cr) {
+ protected void addOptionalDateHeader(ClientRequest cr) {
String date = getHeader(cr, "Date");
if (date == "") {
date = new RFC1123DateConverter().format(new Date());
- cr.getHeaders().add("Date", date);
+ cr.getHeaders().putSingle("Date", date);
}
}
@@ -209,8 +222,4 @@ private String getCanonicalizedResource(ClientRequest cr) {
return result;
}
-
- private String getHeader(ClientRequest cr, String headerKey) {
- return SharedKeyUtils.getHeader(cr, headerKey);
- }
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java
index 0134195d5c3a..37ff1321d5d9 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/blob/implementation/SharedKeyLiteFilter.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.blob.implementation;
@@ -59,6 +59,7 @@ public ClientResponse handle(ClientRequest cr) throws ClientHandlerException {
return this.getNext().handle(cr);
}
+ @Override
public void onBeforeStreamingEntity(ClientRequest clientRequest) {
// All headers should be known at this point, time to sign!
sign(clientRequest);
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java
new file mode 100644
index 000000000000..3623028e5ef1
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/CommaStringBuilder.java
@@ -0,0 +1,47 @@
+package com.microsoft.windowsazure.services.core.utils;
+
+import java.util.List;
+
+public class CommaStringBuilder {
+ private final StringBuilder sb = new StringBuilder();
+
+ public void add(String representation) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(representation);
+ }
+
+ public void addValue(boolean value, String representation) {
+ if (value) {
+ add(representation);
+ }
+ }
+
+ public static String join(List values) {
+ CommaStringBuilder sb = new CommaStringBuilder();
+
+ for (String value : values) {
+ sb.add(value);
+ }
+
+ return sb.toString();
+ }
+
+ public static String join(String... values) {
+ CommaStringBuilder sb = new CommaStringBuilder();
+
+ for (String value : values) {
+ sb.add(value);
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ if (sb.length() == 0)
+ return null;
+ return sb.toString();
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
index a562b7c2122e..234581b023fb 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/Exports.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.core.utils.pipeline;
@@ -25,8 +25,10 @@
public class Exports implements Builder.Exports {
+ @Override
public void register(Registry registry) {
registry.add(new Builder.Factory() {
+ @Override
public ClientConfig create(String profile, Builder builder, Map properties) {
ClientConfig clientConfig = new DefaultClientConfig();
for (Entry entry : properties.entrySet()) {
@@ -37,6 +39,7 @@ public ClientConfig create(String profile, Builder builder, Map
});
registry.add(new Builder.Factory() {
+ @Override
public Client create(String profile, Builder builder, Map properties) {
ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig");
Client client = Client.create(clientConfig);
@@ -45,6 +48,7 @@ public Client create(String profile, Builder builder, Map proper
});
registry.add(new Builder.Factory() {
+ @Override
public HttpURLConnectionClient create(String profile, Builder builder, Map properties) {
ClientConfig clientConfig = (ClientConfig) properties.get("ClientConfig");
HttpURLConnectionClient client = HttpURLConnectionClient.create(clientConfig);
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java
index 0610eb8d7bc9..20d9046c5358 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/HttpURLConnectionClientHandler.java
@@ -27,7 +27,7 @@
import javax.ws.rs.core.MultivaluedMap;
-import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers.EnumCommaStringBuilder;
+import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
@@ -269,7 +269,7 @@ private void setURLConnectionHeaders(MultivaluedMap headers, Htt
urlConnection.setRequestProperty(e.getKey(), ClientRequest.getHeaderValue(vs.get(0)));
}
else {
- EnumCommaStringBuilder sb = new EnumCommaStringBuilder();
+ CommaStringBuilder sb = new CommaStringBuilder();
for (Object v : e.getValue()) {
sb.add(ClientRequest.getHeaderValue(v));
}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
index 95fce9e9dbeb..a36de8dc0247 100644
--- a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/core/utils/pipeline/PipelineHelpers.java
@@ -2,15 +2,15 @@
* Copyright 2011 Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
package com.microsoft.windowsazure.services.core.utils.pipeline;
@@ -28,34 +28,11 @@
public class PipelineHelpers {
public static void ThrowIfError(ClientResponse r) {
- if (r.getStatus() >= 300) {
+ if (r.getStatus() >= 400) {
throw new UniformInterfaceException(r);
}
}
- public static class EnumCommaStringBuilder {
- private final StringBuilder sb = new StringBuilder();
-
- public void add(String representation) {
- if (sb.length() > 0) {
- sb.append(",");
- }
- sb.append(representation);
- }
-
- public void addValue(boolean value, String representation) {
- if (value) {
- add(representation);
- }
- }
-
- public String getValue() {
- if (sb.length() == 0)
- return null;
- return sb.toString();
- }
- }
-
public static WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) {
if (value != null) {
webResource = webResource.queryParam(key, value.toString());
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java
new file mode 100644
index 000000000000..e7582255ef59
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/EdmValueConverter.java
@@ -0,0 +1,7 @@
+package com.microsoft.windowsazure.services.table;
+
+public interface EdmValueConverter {
+ String serialize(String edmType, Object value);
+
+ Object deserialize(String edmType, String value);
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java
new file mode 100644
index 000000000000..ee52dd47bfb2
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/Exports.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import com.microsoft.windowsazure.services.core.Builder;
+import com.microsoft.windowsazure.services.table.implementation.AtomReaderWriter;
+import com.microsoft.windowsazure.services.table.implementation.DefaultEdmValueConterter;
+import com.microsoft.windowsazure.services.table.implementation.DefaultXMLStreamFactory;
+import com.microsoft.windowsazure.services.table.implementation.HttpReaderWriter;
+import com.microsoft.windowsazure.services.table.implementation.MimeReaderWriter;
+import com.microsoft.windowsazure.services.table.implementation.SharedKeyFilter;
+import com.microsoft.windowsazure.services.table.implementation.SharedKeyLiteFilter;
+import com.microsoft.windowsazure.services.table.implementation.TableExceptionProcessor;
+import com.microsoft.windowsazure.services.table.implementation.TableRestProxy;
+import com.microsoft.windowsazure.services.table.implementation.XMLStreamFactory;
+
+public class Exports implements Builder.Exports {
+ @Override
+ public void register(Builder.Registry registry) {
+ registry.add(TableContract.class, TableExceptionProcessor.class);
+ registry.add(TableExceptionProcessor.class);
+ registry.add(TableRestProxy.class);
+ registry.add(SharedKeyLiteFilter.class);
+ registry.add(SharedKeyFilter.class);
+ registry.add(XMLStreamFactory.class, DefaultXMLStreamFactory.class);
+ registry.add(AtomReaderWriter.class);
+ registry.add(MimeReaderWriter.class);
+ registry.add(HttpReaderWriter.class);
+ registry.add(EdmValueConverter.class, DefaultEdmValueConterter.class);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java
new file mode 100644
index 000000000000..49c90e0ddf3d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableConfiguration.java
@@ -0,0 +1,21 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+public class TableConfiguration {
+ public final static String ACCOUNT_NAME = "table.accountName";
+ public final static String ACCOUNT_KEY = "table.accountKey";
+ public final static String URI = "table.uri";
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java
new file mode 100644
index 000000000000..63ec2c1b2086
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableContract.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import com.microsoft.windowsazure.services.core.FilterableService;
+import com.microsoft.windowsazure.services.core.ServiceException;
+import com.microsoft.windowsazure.services.table.models.BatchOperations;
+import com.microsoft.windowsazure.services.table.models.BatchResult;
+import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions;
+import com.microsoft.windowsazure.services.table.models.Entity;
+import com.microsoft.windowsazure.services.table.models.GetEntityResult;
+import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult;
+import com.microsoft.windowsazure.services.table.models.GetTableResult;
+import com.microsoft.windowsazure.services.table.models.InsertEntityResult;
+import com.microsoft.windowsazure.services.table.models.ListTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.ServiceProperties;
+import com.microsoft.windowsazure.services.table.models.TableServiceOptions;
+import com.microsoft.windowsazure.services.table.models.UpdateEntityResult;
+
+public interface TableContract extends FilterableService {
+ GetServicePropertiesResult getServiceProperties() throws ServiceException;
+
+ GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException;
+
+ void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException;
+
+ void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options) throws ServiceException;
+
+ void createTable(String table) throws ServiceException;
+
+ void createTable(String table, TableServiceOptions options) throws ServiceException;
+
+ void deleteTable(String table) throws ServiceException;
+
+ void deleteTable(String table, TableServiceOptions options) throws ServiceException;
+
+ GetTableResult getTable(String table) throws ServiceException;
+
+ GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException;
+
+ QueryTablesResult listTables() throws ServiceException;
+
+ QueryTablesResult listTables(ListTablesOptions options) throws ServiceException;
+
+ QueryTablesResult queryTables() throws ServiceException;
+
+ QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException;
+
+ InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException;
+
+ InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException;
+
+ UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException;
+
+ UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException;
+
+ UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException;
+
+ UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options) throws ServiceException;
+
+ UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException;
+
+ UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException;
+
+ UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException;
+
+ UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException;
+
+ void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException;
+
+ void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options)
+ throws ServiceException;
+
+ GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException;
+
+ GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options)
+ throws ServiceException;
+
+ QueryEntitiesResult queryEntities(String table) throws ServiceException;
+
+ QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException;
+
+ BatchResult batch(BatchOperations operations) throws ServiceException;
+
+ BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException;
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java
new file mode 100644
index 000000000000..2e732e4fc1d6
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/TableService.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import com.microsoft.windowsazure.services.core.Configuration;
+
+public class TableService {
+ private TableService() {
+ }
+
+ public static TableContract create() {
+ return create(null, Configuration.getInstance());
+ }
+
+ public static TableContract create(Configuration config) {
+ return create(null, config);
+ }
+
+ public static TableContract create(String profile) {
+ return create(profile, Configuration.getInstance());
+ }
+
+ public static TableContract create(String profile, Configuration config) {
+ return config.create(profile, TableContract.class);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java
new file mode 100644
index 000000000000..c5803bfb5411
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriter.java
@@ -0,0 +1,307 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.inject.Inject;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+import com.microsoft.windowsazure.services.core.utils.DateFactory;
+import com.microsoft.windowsazure.services.table.EdmValueConverter;
+import com.microsoft.windowsazure.services.table.models.Entity;
+import com.microsoft.windowsazure.services.table.models.Property;
+import com.microsoft.windowsazure.services.table.models.TableEntry;
+
+public class AtomReaderWriter {
+ private final XMLStreamFactory xmlStreamFactory;
+ private final DateFactory dateFactory;
+ private final ISO8601DateConverter iso8601DateConverter;
+ private final EdmValueConverter edmValueConverter;
+
+ @Inject
+ public AtomReaderWriter(XMLStreamFactory xmlStreamFactory, DateFactory dateFactory,
+ ISO8601DateConverter iso8601DateConverter, EdmValueConverter edmValueConverter) {
+ this.xmlStreamFactory = xmlStreamFactory;
+ this.dateFactory = dateFactory;
+ this.iso8601DateConverter = iso8601DateConverter;
+ this.edmValueConverter = edmValueConverter;
+ }
+
+ public InputStream generateTableEntry(String table) {
+ final String tableTemp = table;
+ return generateEntry(new PropertiesWriter() {
+ @Override
+ public void write(XMLStreamWriter writer) throws XMLStreamException {
+ writer.writeStartElement("d:TableName");
+ writer.writeCharacters(tableTemp);
+ writer.writeEndElement(); // d:TableName
+ }
+ });
+ }
+
+ public InputStream generateEntityEntry(Entity entity) {
+ final Entity entityTemp = entity;
+ return generateEntry(new PropertiesWriter() {
+ @Override
+ public void write(XMLStreamWriter writer) throws XMLStreamException {
+ for (Entry entry : entityTemp.getProperties().entrySet()) {
+ writer.writeStartElement("d:" + entry.getKey());
+
+ String edmType = entry.getValue().getEdmType();
+ if (edmType != null) {
+ writer.writeAttribute("m:type", edmType);
+ }
+
+ String value = edmValueConverter.serialize(edmType, entry.getValue().getValue());
+ if (value != null) {
+ writer.writeCharacters(value);
+ }
+
+ writer.writeEndElement(); // property name
+
+ }
+ }
+ });
+ }
+
+ public List parseTableEntries(InputStream stream) {
+ try {
+ XMLStreamReader xmlr = xmlStreamFactory.getReader(stream);
+
+ expect(xmlr, XMLStreamConstants.START_DOCUMENT);
+ expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed");
+
+ List result = new ArrayList();
+ while (!isEndElement(xmlr, "feed")) {
+ // Process "entry" elements only
+ if (isStartElement(xmlr, "entry")) {
+ result.add(parseTableEntry(xmlr));
+ }
+ else {
+ nextSignificant(xmlr);
+ }
+ }
+
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, "feed");
+ expect(xmlr, XMLStreamConstants.END_DOCUMENT);
+
+ return result;
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public TableEntry parseTableEntry(InputStream stream) {
+ try {
+ XMLStreamReader xmlr = xmlStreamFactory.getReader(stream);
+
+ expect(xmlr, XMLStreamConstants.START_DOCUMENT);
+ TableEntry result = parseTableEntry(xmlr);
+ expect(xmlr, XMLStreamConstants.END_DOCUMENT);
+
+ return result;
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public List parseEntityEntries(InputStream stream) {
+ try {
+ XMLStreamReader xmlr = xmlStreamFactory.getReader(stream);
+
+ expect(xmlr, XMLStreamConstants.START_DOCUMENT);
+ expect(xmlr, XMLStreamConstants.START_ELEMENT, "feed");
+
+ List result = new ArrayList();
+ while (!isEndElement(xmlr, "feed")) {
+ // Process "entry" elements only
+ if (isStartElement(xmlr, "entry")) {
+ result.add(parseEntityEntry(xmlr));
+ }
+ else {
+ nextSignificant(xmlr);
+ }
+ }
+
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, "feed");
+ expect(xmlr, XMLStreamConstants.END_DOCUMENT);
+
+ return result;
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Entity parseEntityEntry(InputStream stream) {
+ try {
+ XMLStreamReader xmlr = xmlStreamFactory.getReader(stream);
+
+ expect(xmlr, XMLStreamConstants.START_DOCUMENT);
+ Entity result = parseEntityEntry(xmlr);
+ expect(xmlr, XMLStreamConstants.END_DOCUMENT);
+
+ return result;
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private interface PropertiesWriter {
+ void write(XMLStreamWriter writer) throws XMLStreamException;
+ }
+
+ private InputStream generateEntry(PropertiesWriter propertiesWriter) {
+ try {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ XMLStreamWriter writer = xmlStreamFactory.getWriter(stream);
+ writer.writeStartDocument("utf-8", "1.0");
+
+ writer.writeStartElement("entry");
+ writer.writeAttribute("xmlns:d", "http://schemas.microsoft.com/ado/2007/08/dataservices");
+ writer.writeAttribute("xmlns:m", "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata");
+ writer.writeAttribute("xmlns", "http://www.w3.org/2005/Atom");
+
+ writer.writeStartElement("title");
+ writer.writeEndElement(); // title
+
+ writer.writeStartElement("updated");
+ writer.writeCharacters(iso8601DateConverter.format(dateFactory.getDate()));
+ writer.writeEndElement(); // updated
+
+ writer.writeStartElement("author");
+ writer.writeStartElement("name");
+ writer.writeEndElement(); // name
+ writer.writeEndElement(); // author
+
+ writer.writeStartElement("id");
+ writer.writeEndElement(); // id
+
+ writer.writeStartElement("content");
+ writer.writeAttribute("type", "application/xml");
+
+ writer.writeStartElement("m:properties");
+ propertiesWriter.write(writer);
+ writer.writeEndElement(); // m:properties
+
+ writer.writeEndElement(); // content
+
+ writer.writeEndElement(); // entry
+
+ writer.writeEndDocument();
+ writer.close();
+
+ return new ByteArrayInputStream(stream.toByteArray());
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private TableEntry parseTableEntry(XMLStreamReader xmlr) throws XMLStreamException {
+ TableEntry result = new TableEntry();
+
+ expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry");
+
+ while (!isEndElement(xmlr, "entry")) {
+ if (isStartElement(xmlr, "properties")) {
+ Map properties = parseEntryProperties(xmlr);
+
+ result.setName((String) properties.get("TableName").getValue());
+ }
+ else {
+ nextSignificant(xmlr);
+ }
+ }
+
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry");
+
+ return result;
+ }
+
+ private Entity parseEntityEntry(XMLStreamReader xmlr) throws XMLStreamException {
+ Entity result = new Entity();
+
+ result.setEtag(xmlr.getAttributeValue(null, "etag"));
+ expect(xmlr, XMLStreamConstants.START_ELEMENT, "entry");
+
+ while (!isEndElement(xmlr, "entry")) {
+ if (isStartElement(xmlr, "properties")) {
+ result.setProperties(parseEntryProperties(xmlr));
+ }
+ else {
+ nextSignificant(xmlr);
+ }
+ }
+
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, "entry");
+
+ return result;
+ }
+
+ private Map parseEntryProperties(XMLStreamReader xmlr) throws XMLStreamException {
+ Map result = new HashMap();
+
+ expect(xmlr, XMLStreamConstants.START_ELEMENT, "properties");
+
+ while (!isEndElement(xmlr, "properties")) {
+ String name = xmlr.getLocalName();
+ String edmType = xmlr.getAttributeValue(null, "type");
+
+ xmlr.next();
+ String serializedValue = xmlr.getText();
+ Object value = edmValueConverter.deserialize(edmType, serializedValue);
+
+ result.put(name, new Property().setEdmType(edmType).setValue(value));
+
+ nextSignificant(xmlr);
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, name);
+ }
+
+ expect(xmlr, XMLStreamConstants.END_ELEMENT, "properties");
+
+ return result;
+ }
+
+ private void nextSignificant(XMLStreamReader xmlr) throws XMLStreamException {
+ if (!xmlr.hasNext())
+ return;
+ xmlr.next();
+
+ while (xmlr.isCharacters()) {
+ if (!xmlr.hasNext())
+ return;
+ xmlr.next();
+ }
+ }
+
+ private boolean isStartElement(XMLStreamReader xmlr, String localName) {
+ return xmlr.isStartElement() && localName.equals(xmlr.getLocalName());
+ }
+
+ private boolean isEndElement(XMLStreamReader xmlr, String localName) {
+ return xmlr.isEndElement() && localName.equals(xmlr.getLocalName());
+ }
+
+ private void expect(XMLStreamReader xmlr, int eventType) throws XMLStreamException {
+ expect(xmlr, eventType, null);
+ }
+
+ private void expect(XMLStreamReader xmlr, int eventType, String localName) throws XMLStreamException {
+ xmlr.require(eventType, null, localName);
+ nextSignificant(xmlr);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java
new file mode 100644
index 000000000000..b062b3394ab9
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultEdmValueConterter.java
@@ -0,0 +1,65 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.text.ParseException;
+import java.util.Date;
+
+import javax.inject.Inject;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+import com.microsoft.windowsazure.services.table.EdmValueConverter;
+import com.microsoft.windowsazure.services.table.models.EdmType;
+
+public class DefaultEdmValueConterter implements EdmValueConverter {
+
+ private final ISO8601DateConverter iso8601DateConverter;
+
+ @Inject
+ public DefaultEdmValueConterter(ISO8601DateConverter iso8601DateConverter) {
+ this.iso8601DateConverter = iso8601DateConverter;
+ }
+
+ @Override
+ public String serialize(String edmType, Object value) {
+ if (value == null)
+ return null;
+
+ String serializedValue;
+ if (value instanceof Date) {
+ serializedValue = iso8601DateConverter.format((Date) value);
+ }
+ else {
+ serializedValue = value.toString();
+ }
+
+ return serializedValue;
+ }
+
+ @Override
+ public Object deserialize(String edmType, String value) {
+ if (edmType == null)
+ return value;
+
+ if (EdmType.DATETIME.equals(edmType)) {
+ try {
+ return iso8601DateConverter.parse(value);
+ }
+ catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ else if (EdmType.BOOLEAN.equals(edmType)) {
+ return Boolean.parseBoolean(value);
+ }
+ else if (EdmType.DOUBLE.equals(edmType)) {
+ return Double.parseDouble(value);
+ }
+ else if (EdmType.INT32.equals(edmType)) {
+ return Integer.parseInt(value);
+ }
+ else if (EdmType.INT64.equals(edmType)) {
+ return Long.parseLong(value);
+ }
+
+ return value;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java
new file mode 100644
index 000000000000..8504c88fa349
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/DefaultXMLStreamFactory.java
@@ -0,0 +1,40 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+public class DefaultXMLStreamFactory implements XMLStreamFactory {
+ private final XMLOutputFactory xmlOutputFactory;
+ private final XMLInputFactory xmlInputFactory;
+
+ public DefaultXMLStreamFactory() {
+ this.xmlOutputFactory = XMLOutputFactory.newInstance();
+ this.xmlInputFactory = XMLInputFactory.newInstance();
+ }
+
+ @Override
+ public XMLStreamWriter getWriter(OutputStream stream) {
+ try {
+ return xmlOutputFactory.createXMLStreamWriter(stream, "UTF-8");
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public XMLStreamReader getReader(InputStream stream) {
+ try {
+ return xmlInputFactory.createXMLStreamReader(stream);
+ }
+ catch (XMLStreamException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java
new file mode 100644
index 000000000000..458acf6e0cd9
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/HttpReaderWriter.java
@@ -0,0 +1,168 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.util.Enumeration;
+
+import javax.activation.DataSource;
+import javax.inject.Inject;
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+
+import com.sun.mail.util.LineInputStream;
+
+public class HttpReaderWriter {
+
+ @Inject
+ public HttpReaderWriter() {
+ }
+
+ public StatusLine parseStatusLine(DataSource ds) {
+ try {
+ LineInputStream stream = new LineInputStream(ds.getInputStream());
+ String line = stream.readLine();
+ StringReader lineReader = new StringReader(line);
+
+ expect(lineReader, "HTTP/1.1");
+ expect(lineReader, " ");
+ String statusString = extractInput(lineReader, ' ');
+ String reason = extractInput(lineReader, -1);
+
+ return new StatusLine().setStatus(Integer.parseInt(statusString)).setReason(reason);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public InternetHeaders parseHeaders(DataSource ds) {
+ try {
+ return new InternetHeaders(ds.getInputStream());
+ }
+ catch (MessagingException e) {
+ throw new RuntimeException(e);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public InputStream parseEntity(DataSource ds) {
+ try {
+ return ds.getInputStream();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void appendMethod(OutputStream stream, String verb, URI uri) {
+ try {
+ String method = String.format("%s %s %s\r\n", verb, uri, "HTTP/1.1");
+ stream.write(method.getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void appendHeaders(OutputStream stream, InternetHeaders headers) {
+ try {
+ // Headers
+ Enumeration e = headers.getAllHeaders();
+ while (e.hasMoreElements()) {
+ Header header = e.nextElement();
+
+ String headerLine = String.format("%s: %s\r\n", header.getName(), header.getValue());
+ stream.write(headerLine.getBytes("UTF-8"));
+ }
+
+ // Empty line
+ stream.write("\r\n".getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void appendEntity(OutputStream stream, InputStream entity) {
+ try {
+ byte[] buffer = new byte[1024];
+ while (true) {
+ int n = entity.read(buffer);
+ if (n == -1)
+ break;
+ stream.write(buffer, 0, n);
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void expect(Reader reader, String string) {
+ try {
+ for (int i = 0; i < string.length(); i++) {
+ int ch = reader.read();
+ if (ch < 0)
+ throw new RuntimeException(String.format("Expected '%s', found '%s' instead", string,
+ string.substring(0, i) + ch));
+ }
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String extractInput(Reader reader, int delimiter) {
+ try {
+ StringBuilder sb = new StringBuilder();
+ while (true) {
+ int ch = reader.read();
+ if (ch == -1 || ch == delimiter)
+ break;
+
+ sb.append((char) ch);
+ }
+ return sb.toString();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public class StatusLine {
+ private int status;
+ private String reason;
+
+ public int getStatus() {
+ return status;
+ }
+
+ public StatusLine setStatus(int status) {
+ this.status = status;
+ return this;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public StatusLine setReason(String reason) {
+ this.reason = reason;
+ return this;
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java
new file mode 100644
index 000000000000..22c099a52fee
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/InputStreamDataSource.java
@@ -0,0 +1,38 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.DataSource;
+
+public class InputStreamDataSource implements DataSource {
+ private final InputStream stream;
+ private final String contentType;
+
+ public InputStreamDataSource(InputStream stream, String contentType) {
+ this.stream = stream;
+ this.contentType = contentType;
+
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return stream;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java
new file mode 100644
index 000000000000..9d2282ab08e6
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/MimeReaderWriter.java
@@ -0,0 +1,147 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.inject.Inject;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.MultipartDataSource;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePartDataSource;
+
+public class MimeReaderWriter {
+
+ @Inject
+ public MimeReaderWriter() {
+ }
+
+ public MimeMultipart getMimeMultipart(List bodyPartContents) {
+ try {
+ return getMimeMultipartCore(bodyPartContents);
+ }
+ catch (MessagingException e) {
+ throw new RuntimeException(e);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private MimeMultipart getMimeMultipartCore(List bodyPartContents) throws MessagingException,
+ IOException {
+ // Create unique part boundary strings
+ String batchId = String.format("batch_%s", UUID.randomUUID().toString());
+ String changeSet = String.format("changeset_%s", UUID.randomUUID().toString());
+
+ //
+ // Build inner list of change sets containing the list of body part content
+ //
+ MimeMultipart changeSets = new MimeMultipart(new SetBoundaryMultipartDataSource(changeSet));
+
+ for (DataSource bodyPart : bodyPartContents) {
+ MimeBodyPart mimeBodyPart = new MimeBodyPart();
+
+ mimeBodyPart.setDataHandler(new DataHandler(bodyPart));
+ mimeBodyPart.setHeader("Content-Type", bodyPart.getContentType());
+ mimeBodyPart.setHeader("Content-Transfer-Encoding", "binary");
+
+ changeSets.addBodyPart(mimeBodyPart);
+ }
+
+ //
+ // Build outer "batch" body part
+ //
+ MimeBodyPart batchbody = new MimeBodyPart();
+ batchbody.setContent(changeSets);
+ //Note: Both content type and encoding need to be set *after* setting content, because
+ // MimeBodyPart implementation replaces them when calling "setContent".
+ batchbody.setHeader("Content-Type", changeSets.getContentType());
+
+ //
+ // Build outer "batch" multipart
+ //
+ MimeMultipart batch = new MimeMultipart(new SetBoundaryMultipartDataSource(batchId));
+ batch.addBodyPart(batchbody);
+ return batch;
+ }
+
+ /**
+ * The only purpose of this class is to force the boundary of a MimeMultipart instance.
+ * This is done by simple passing an instance of this class to the constructor of MimeMultipart.
+ */
+ private class SetBoundaryMultipartDataSource implements MultipartDataSource {
+
+ private final String boundary;
+
+ public SetBoundaryMultipartDataSource(String boundary) {
+ this.boundary = boundary;
+ }
+
+ @Override
+ public String getContentType() {
+ return "multipart/mixed; boundary=" + boundary;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return null;
+ }
+
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public BodyPart getBodyPart(int index) throws MessagingException {
+ return null;
+ }
+ }
+
+ public List parseParts(final InputStream entityInputStream, final String contentType) {
+ try {
+ return parsePartsCore(entityInputStream, contentType);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ catch (MessagingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private List parsePartsCore(InputStream entityInputStream, String contentType)
+ throws MessagingException, IOException {
+ DataSource ds = new InputStreamDataSource(entityInputStream, contentType);
+ MimeMultipart batch = new MimeMultipart(ds);
+ MimeBodyPart batchBody = (MimeBodyPart) batch.getBodyPart(0);
+
+ MimeMultipart changeSets = new MimeMultipart(new MimePartDataSource(batchBody));
+
+ List result = new ArrayList();
+ for (int i = 0; i < changeSets.getCount(); i++) {
+ BodyPart part = changeSets.getBodyPart(i);
+
+ result.add(new InputStreamDataSource(part.getInputStream(), part.getContentType()));
+ }
+ return result;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java
new file mode 100644
index 000000000000..79305ce758c8
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyFilter.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.util.List;
+
+import javax.inject.Named;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.microsoft.windowsazure.services.blob.implementation.SharedKeyUtils;
+import com.microsoft.windowsazure.services.blob.implementation.SharedKeyUtils.QueryParam;
+import com.microsoft.windowsazure.services.table.TableConfiguration;
+import com.sun.jersey.api.client.ClientRequest;
+
+public class SharedKeyFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyFilter {
+ private static Log log = LogFactory.getLog(SharedKeyFilter.class);
+
+ public SharedKeyFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName,
+ @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) {
+ super(accountName, accountKey);
+ }
+
+ /*
+ * StringToSign = VERB + "\n" +
+ * Content-MD5 + "\n" +
+ * Content-Type + "\n" +
+ * Date + "\n" +
+ * CanonicalizedResource;
+ */
+ @Override
+ public void sign(ClientRequest cr) {
+ // gather signed material
+ addOptionalDateHeader(cr);
+
+ // build signed string
+ String stringToSign = cr.getMethod() + "\n" + getHeader(cr, "Content-MD5") + "\n"
+ + getHeader(cr, "Content-Type") + "\n" + getHeader(cr, "Date") + "\n";
+
+ stringToSign += getCanonicalizedResource(cr);
+
+ if (log.isDebugEnabled()) {
+ log.debug(String.format("String to sign: \"%s\"", stringToSign));
+ }
+ //TODO: Remove or comment the following line
+ //System.out.println(String.format("String to sign: \"%s\"", stringToSign));
+
+ String signature = this.getSigner().sign(stringToSign);
+ cr.getHeaders().putSingle("Authorization", "SharedKey " + this.getAccountName() + ":" + signature);
+ }
+
+ /**
+ * This format supports Shared Key and Shared Key Lite for all versions of the Table service, and Shared Key Lite
+ * for the 2009-09-19 version of the Blob and Queue services. This format is identical to that used with previous
+ * versions of the storage services. Construct the CanonicalizedResource string in this format as follows:
+ *
+ * 1. Beginning with an empty string (""), append a forward slash (/), followed by the name of the account that owns
+ * the resource being accessed.
+ *
+ * 2. Append the resource's encoded URI path. If the request URI addresses a component of the resource, append the
+ * appropriate query string. The query string should include the question mark and the comp parameter (for example,
+ * ?comp=metadata). No other parameters should be included on the query string.
+ */
+ private String getCanonicalizedResource(ClientRequest cr) {
+ String result = "/" + this.getAccountName();
+
+ result += cr.getURI().getPath();
+
+ List queryParams = SharedKeyUtils.getQueryParams(cr.getURI().getQuery());
+ for (QueryParam p : queryParams) {
+ if ("comp".equals(p.getName())) {
+ result += "?" + p.getName() + "=" + p.getValues().get(0);
+ }
+ }
+ return result;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java
new file mode 100644
index 000000000000..094fe429145a
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/SharedKeyLiteFilter.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import javax.inject.Named;
+
+import com.microsoft.windowsazure.services.table.TableConfiguration;
+
+public class SharedKeyLiteFilter extends com.microsoft.windowsazure.services.blob.implementation.SharedKeyLiteFilter {
+ public SharedKeyLiteFilter(@Named(TableConfiguration.ACCOUNT_NAME) String accountName,
+ @Named(TableConfiguration.ACCOUNT_KEY) String accountKey) {
+ super(accountName, accountKey);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java
new file mode 100644
index 000000000000..c8bf695b7cdd
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableExceptionProcessor.java
@@ -0,0 +1,491 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import javax.inject.Inject;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.microsoft.windowsazure.services.core.ServiceException;
+import com.microsoft.windowsazure.services.core.ServiceFilter;
+import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory;
+import com.microsoft.windowsazure.services.table.TableContract;
+import com.microsoft.windowsazure.services.table.models.BatchOperations;
+import com.microsoft.windowsazure.services.table.models.BatchResult;
+import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions;
+import com.microsoft.windowsazure.services.table.models.Entity;
+import com.microsoft.windowsazure.services.table.models.GetEntityResult;
+import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult;
+import com.microsoft.windowsazure.services.table.models.GetTableResult;
+import com.microsoft.windowsazure.services.table.models.InsertEntityResult;
+import com.microsoft.windowsazure.services.table.models.ListTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.ServiceProperties;
+import com.microsoft.windowsazure.services.table.models.TableServiceOptions;
+import com.microsoft.windowsazure.services.table.models.UpdateEntityResult;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.UniformInterfaceException;
+
+public class TableExceptionProcessor implements TableContract {
+ private static Log log = LogFactory.getLog(TableExceptionProcessor.class);
+ private final TableContract service;
+
+ @Inject
+ public TableExceptionProcessor(TableRestProxy service) {
+ this.service = service;
+ }
+
+ public TableExceptionProcessor(TableContract service) {
+ this.service = service;
+ }
+
+ @Override
+ public TableContract withFilter(ServiceFilter filter) {
+ return new TableExceptionProcessor(service.withFilter(filter));
+ }
+
+ private ServiceException processCatch(ServiceException e) {
+ log.warn(e.getMessage(), e.getCause());
+ return ServiceExceptionFactory.process("table", e);
+ }
+
+ @Override
+ public GetServicePropertiesResult getServiceProperties() throws ServiceException {
+ try {
+ return service.getServiceProperties();
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException {
+ try {
+ return service.getServiceProperties(options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException {
+ try {
+ service.setServiceProperties(serviceProperties);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ service.setServiceProperties(serviceProperties, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void createTable(String table) throws ServiceException {
+ try {
+ service.createTable(table);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void createTable(String table, TableServiceOptions options) throws ServiceException {
+ try {
+ service.createTable(table, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public GetTableResult getTable(String table) throws ServiceException {
+ try {
+ return service.getTable(table);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException {
+ try {
+ return service.getTable(table, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void deleteTable(String table) throws ServiceException {
+ try {
+ service.deleteTable(table);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void deleteTable(String table, TableServiceOptions options) throws ServiceException {
+ try {
+ service.deleteTable(table, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryTablesResult queryTables() throws ServiceException {
+ try {
+ return service.queryTables();
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException {
+ try {
+ return service.queryTables(options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryTablesResult listTables() throws ServiceException {
+ try {
+ return service.listTables();
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException {
+ try {
+ return service.listTables(options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException {
+ try {
+ return service.insertEntity(table, entity);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.insertEntity(table, entity, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException {
+ try {
+ return service.updateEntity(table, entity);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.updateEntity(table, entity, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException {
+ try {
+ return service.mergeEntity(table, entity);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.mergeEntity(table, entity, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException {
+ try {
+ return service.insertOrReplaceEntity(table, entity);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.insertOrReplaceEntity(table, entity, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException {
+ try {
+ return service.insertOrMergeEntity(table, entity);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.insertOrMergeEntity(table, entity, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException {
+ try {
+ service.deleteEntity(table, partitionKey, rowKey);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options)
+ throws ServiceException {
+ try {
+ service.deleteEntity(table, partitionKey, rowKey, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException {
+ try {
+ return service.getEntity(table, partitionKey, rowKey);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options)
+ throws ServiceException {
+ try {
+ return service.getEntity(table, partitionKey, rowKey, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryEntitiesResult queryEntities(String table) throws ServiceException {
+ try {
+ return service.queryEntities(table);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException {
+ try {
+ return service.queryEntities(table, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public BatchResult batch(BatchOperations operations) throws ServiceException {
+ try {
+ return service.batch(operations);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+
+ @Override
+ public BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException {
+ try {
+ return service.batch(operations, options);
+ }
+ catch (UniformInterfaceException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ catch (ClientHandlerException e) {
+ throw processCatch(new ServiceException(e));
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java
new file mode 100644
index 000000000000..096c5b4ec312
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/TableRestProxy.java
@@ -0,0 +1,767 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.List;
+
+import javax.activation.DataSource;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.mail.Header;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeMultipart;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter;
+import com.microsoft.windowsazure.services.core.ServiceException;
+import com.microsoft.windowsazure.services.core.ServiceFilter;
+import com.microsoft.windowsazure.services.core.utils.CommaStringBuilder;
+import com.microsoft.windowsazure.services.core.utils.DateFactory;
+import com.microsoft.windowsazure.services.core.utils.ServiceExceptionFactory;
+import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter;
+import com.microsoft.windowsazure.services.core.utils.pipeline.HttpURLConnectionClient;
+import com.microsoft.windowsazure.services.core.utils.pipeline.PipelineHelpers;
+import com.microsoft.windowsazure.services.table.TableConfiguration;
+import com.microsoft.windowsazure.services.table.TableContract;
+import com.microsoft.windowsazure.services.table.implementation.HttpReaderWriter.StatusLine;
+import com.microsoft.windowsazure.services.table.models.BatchOperations;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.DeleteEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOrMergeEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.InsertOrReplaceEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.MergeEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.Operation;
+import com.microsoft.windowsazure.services.table.models.BatchOperations.UpdateEntityOperation;
+import com.microsoft.windowsazure.services.table.models.BatchResult;
+import com.microsoft.windowsazure.services.table.models.BatchResult.DeleteEntity;
+import com.microsoft.windowsazure.services.table.models.BatchResult.Entry;
+import com.microsoft.windowsazure.services.table.models.BatchResult.Error;
+import com.microsoft.windowsazure.services.table.models.BatchResult.InsertEntity;
+import com.microsoft.windowsazure.services.table.models.BatchResult.UpdateEntity;
+import com.microsoft.windowsazure.services.table.models.BinaryFilter;
+import com.microsoft.windowsazure.services.table.models.ConstantFilter;
+import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions;
+import com.microsoft.windowsazure.services.table.models.Entity;
+import com.microsoft.windowsazure.services.table.models.Filter;
+import com.microsoft.windowsazure.services.table.models.GetEntityResult;
+import com.microsoft.windowsazure.services.table.models.GetServicePropertiesResult;
+import com.microsoft.windowsazure.services.table.models.GetTableResult;
+import com.microsoft.windowsazure.services.table.models.InsertEntityResult;
+import com.microsoft.windowsazure.services.table.models.ListTablesOptions;
+import com.microsoft.windowsazure.services.table.models.LitteralFilter;
+import com.microsoft.windowsazure.services.table.models.Query;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryTablesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.RawStringFilter;
+import com.microsoft.windowsazure.services.table.models.ServiceProperties;
+import com.microsoft.windowsazure.services.table.models.TableServiceOptions;
+import com.microsoft.windowsazure.services.table.models.UnaryFilter;
+import com.microsoft.windowsazure.services.table.models.UpdateEntityResult;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.WebResource.Builder;
+import com.sun.jersey.core.header.InBoundHeaders;
+
+public class TableRestProxy implements TableContract {
+ private static final String API_VERSION = "2011-08-18";
+ private final HttpURLConnectionClient channel;
+ private final String url;
+ private final RFC1123DateConverter dateMapper;
+ private final ISO8601DateConverter iso8601DateConverter;
+ private final DateFactory dateFactory;
+ private final ServiceFilter[] filters;
+ private final SharedKeyFilter filter;
+ private final AtomReaderWriter atomReaderWriter;
+ private final MimeReaderWriter mimeReaderWriter;
+ private final HttpReaderWriter httpReaderWriter;
+
+ @Inject
+ public TableRestProxy(HttpURLConnectionClient channel, @Named(TableConfiguration.URI) String url,
+ SharedKeyFilter filter, DateFactory dateFactory, ISO8601DateConverter iso8601DateConverter,
+ AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter, HttpReaderWriter httpReaderWriter) {
+
+ this.channel = channel;
+ this.url = url;
+ this.filter = filter;
+ this.dateMapper = new RFC1123DateConverter();
+ this.iso8601DateConverter = iso8601DateConverter;
+ this.filters = new ServiceFilter[0];
+ this.dateFactory = dateFactory;
+ this.atomReaderWriter = atomReaderWriter;
+ this.mimeReaderWriter = mimeReaderWriter;
+ this.httpReaderWriter = httpReaderWriter;
+ channel.addFilter(filter);
+ }
+
+ public TableRestProxy(HttpURLConnectionClient channel, ServiceFilter[] filters, String url, SharedKeyFilter filter,
+ DateFactory dateFactory, AtomReaderWriter atomReaderWriter, MimeReaderWriter mimeReaderWriter,
+ HttpReaderWriter httpReaderWriter, RFC1123DateConverter dateMapper,
+ ISO8601DateConverter iso8601DateConverter) {
+
+ this.channel = channel;
+ this.filters = filters;
+ this.url = url;
+ this.filter = filter;
+ this.dateFactory = dateFactory;
+ this.atomReaderWriter = atomReaderWriter;
+ this.mimeReaderWriter = mimeReaderWriter;
+ this.httpReaderWriter = httpReaderWriter;
+ this.dateMapper = dateMapper;
+ this.iso8601DateConverter = iso8601DateConverter;
+ }
+
+ @Override
+ public TableContract withFilter(ServiceFilter filter) {
+ ServiceFilter[] newFilters = Arrays.copyOf(filters, filters.length + 1);
+ newFilters[filters.length] = filter;
+ return new TableRestProxy(this.channel, newFilters, this.url, this.filter, this.dateFactory,
+ this.atomReaderWriter, this.mimeReaderWriter, this.httpReaderWriter, this.dateMapper,
+ this.iso8601DateConverter);
+ }
+
+ private void ThrowIfError(ClientResponse r) {
+ PipelineHelpers.ThrowIfError(r);
+ }
+
+ private String encodeODataURIValue(String value) {
+ //TODO: Unclear if OData value in URI's need to be encoded or not
+ return value;
+ }
+
+ private List encodeODataURIValues(List values) {
+ List list = new ArrayList();
+ for (String value : values) {
+ list.add(encodeODataURIValue(value));
+ }
+ return list;
+ }
+
+ private String getEntityPath(String table, String partitionKey, String rowKey) {
+ return table + "(" + "PartitionKey='" + partitionKey + "',RowKey='" + rowKey + "')";
+ }
+
+ private WebResource addOptionalQueryParam(WebResource webResource, String key, Object value) {
+ return PipelineHelpers.addOptionalQueryParam(webResource, key, value);
+ }
+
+ private WebResource addOptionalQuery(WebResource webResource, Query query) {
+ if (query == null)
+ return webResource;
+
+ if (query.getSelectFields() != null && query.getSelectFields().size() > 0) {
+ webResource = addOptionalQueryParam(webResource, "$select",
+ CommaStringBuilder.join(encodeODataURIValues(query.getSelectFields())));
+ }
+
+ if (query.getTop() != null) {
+ webResource = addOptionalQueryParam(webResource, "$top", encodeODataURIValue(query.getTop().toString()));
+ }
+
+ if (query.getFilter() != null) {
+ webResource = addOptionalQueryParam(webResource, "$filter", buildFilterExpression(query.getFilter()));
+ }
+
+ if (query.getOrderByFields() != null) {
+ webResource = addOptionalQueryParam(webResource, "$orderby",
+ CommaStringBuilder.join(encodeODataURIValues(query.getOrderByFields())));
+ }
+
+ return webResource;
+ }
+
+ private String buildFilterExpression(Filter filter) {
+ StringBuilder sb = new StringBuilder();
+ buildFilterExpression(filter, sb);
+ return sb.toString();
+ }
+
+ private void buildFilterExpression(Filter filter, StringBuilder sb) {
+ if (filter == null)
+ return;
+
+ if (filter instanceof LitteralFilter) {
+ sb.append(((LitteralFilter) filter).getLitteral());
+ }
+ else if (filter instanceof ConstantFilter) {
+ sb.append("'");
+ sb.append(((ConstantFilter) filter).getValue());
+ sb.append("'");
+ }
+ else if (filter instanceof UnaryFilter) {
+ sb.append(((UnaryFilter) filter).getOperator());
+ sb.append("(");
+ buildFilterExpression(((UnaryFilter) filter).getOperand(), sb);
+ sb.append(")");
+ }
+ else if (filter instanceof BinaryFilter) {
+ sb.append("(");
+ buildFilterExpression(((BinaryFilter) filter).getLeft(), sb);
+ sb.append(" ");
+ sb.append(((BinaryFilter) filter).getOperator());
+ sb.append(" ");
+ buildFilterExpression(((BinaryFilter) filter).getRight(), sb);
+ sb.append(")");
+ }
+ else if (filter instanceof RawStringFilter) {
+ sb.append(((RawStringFilter) filter).getRawString());
+ }
+ }
+
+ private Builder addOptionalHeader(Builder builder, String name, Object value) {
+ return PipelineHelpers.addOptionalHeader(builder, name, value);
+ }
+
+ private WebResource.Builder addTableRequestHeaders(WebResource.Builder builder) {
+ builder = addOptionalHeader(builder, "x-ms-version", API_VERSION);
+ builder = addOptionalHeader(builder, "DataServiceVersion", "1.0;NetFx");
+ builder = addOptionalHeader(builder, "MaxDataServiceVersion", "2.0;NetFx");
+ builder = addOptionalHeader(builder, "Accept", "application/atom+xml,application/xml");
+ builder = addOptionalHeader(builder, "Accept-Charset", "UTF-8");
+ return builder;
+ }
+
+ private WebResource.Builder addIfMatchHeader(WebResource.Builder builder, String eTag) {
+ builder = addOptionalHeader(builder, "If-Match", eTag == null ? "*" : eTag);
+ return builder;
+ }
+
+ private WebResource getResource(TableServiceOptions options) {
+ WebResource webResource = channel.resource(url).path("/");
+ webResource = addOptionalQueryParam(webResource, "timeout", options.getTimeout());
+ for (ServiceFilter filter : filters) {
+ webResource.addFilter(new ClientFilterAdapter(filter));
+ }
+
+ return webResource;
+ }
+
+ @Override
+ public GetServicePropertiesResult getServiceProperties() throws ServiceException {
+ return getServiceProperties(new TableServiceOptions());
+ }
+
+ @Override
+ public GetServicePropertiesResult getServiceProperties(TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("/").queryParam("resType", "service")
+ .queryParam("comp", "properties");
+
+ WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION);
+
+ GetServicePropertiesResult result = new GetServicePropertiesResult();
+ result.setValue(builder.get(ServiceProperties.class));
+ return result;
+ }
+
+ @Override
+ public void setServiceProperties(ServiceProperties serviceProperties) throws ServiceException {
+ setServiceProperties(serviceProperties, new TableServiceOptions());
+ }
+
+ @Override
+ public void setServiceProperties(ServiceProperties serviceProperties, TableServiceOptions options)
+ throws ServiceException {
+ WebResource webResource = getResource(options).path("/").queryParam("resType", "service")
+ .queryParam("comp", "properties");
+
+ WebResource.Builder builder = webResource.header("x-ms-version", API_VERSION);
+
+ builder.put(serviceProperties);
+ }
+
+ @Override
+ public GetTableResult getTable(String table) throws ServiceException {
+ return getTable(table, new TableServiceOptions());
+ }
+
+ @Override
+ public GetTableResult getTable(String table, TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("Tables" + "('" + table + "')");
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ ClientResponse response = builder.get(ClientResponse.class);
+ ThrowIfError(response);
+
+ GetTableResult result = new GetTableResult();
+ result.setTableEntry(atomReaderWriter.parseTableEntry(response.getEntityInputStream()));
+ return result;
+ }
+
+ @Override
+ public QueryTablesResult listTables() throws ServiceException {
+ return listTables(new ListTablesOptions());
+ }
+
+ @Override
+ public QueryTablesResult listTables(ListTablesOptions options) throws ServiceException {
+ // Append Max char to end '{' is 1 + 'z' in AsciiTable ==> uppperBound is prefix + '{'
+ Filter filter = Filter.and(Filter.ge(Filter.litteral("TableName"), Filter.constant(options.getPrefix())),
+ Filter.le(Filter.litteral("TableName"), Filter.constant(options.getPrefix() + "{")));
+
+ QueryTablesOptions queryTableOptions = new QueryTablesOptions();
+ queryTableOptions.setTimeout(options.getTimeout());
+ queryTableOptions.setQuery(new Query().setFilter(filter));
+ return queryTables(queryTableOptions);
+ }
+
+ @Override
+ public QueryTablesResult queryTables() throws ServiceException {
+ return queryTables(new QueryTablesOptions());
+ }
+
+ @Override
+ public QueryTablesResult queryTables(QueryTablesOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("Tables");
+ webResource = addOptionalQuery(webResource, options.getQuery());
+ webResource = addOptionalQueryParam(webResource, "NextTableName", options.getNextTableName());
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ ClientResponse response = builder.get(ClientResponse.class);
+ ThrowIfError(response);
+
+ QueryTablesResult result = new QueryTablesResult();
+ result.setNextTableName(response.getHeaders().getFirst("x-ms-continuation-NextTableName"));
+ result.setTables(atomReaderWriter.parseTableEntries(response.getEntityInputStream()));
+
+ return result;
+ }
+
+ @Override
+ public void createTable(String table) throws ServiceException {
+ createTable(table, new TableServiceOptions());
+
+ }
+
+ @Override
+ public void createTable(String table, TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("Tables");
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ builder.entity(atomReaderWriter.generateTableEntry(table), "application/atom+xml");
+
+ ClientResponse response = builder.post(ClientResponse.class);
+ ThrowIfError(response);
+ }
+
+ @Override
+ public void deleteTable(String table) throws ServiceException {
+ deleteTable(table, new TableServiceOptions());
+ }
+
+ @Override
+ public void deleteTable(String table, TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("Tables" + "('" + table + "')");
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+ builder = addOptionalHeader(builder, "Content-Type", "application/atom+xml");
+
+ ClientResponse response = builder.delete(ClientResponse.class);
+ ThrowIfError(response);
+ }
+
+ @Override
+ public InsertEntityResult insertEntity(String table, Entity entity) throws ServiceException {
+ return insertEntity(table, entity, new TableServiceOptions());
+ }
+
+ @Override
+ public InsertEntityResult insertEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ WebResource webResource = getResource(options).path(table);
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml");
+
+ ClientResponse response = builder.post(ClientResponse.class);
+ ThrowIfError(response);
+
+ InsertEntityResult result = new InsertEntityResult();
+ result.setEntity(atomReaderWriter.parseEntityEntry(response.getEntityInputStream()));
+
+ return result;
+ }
+
+ @Override
+ public UpdateEntityResult updateEntity(String table, Entity entity) throws ServiceException {
+ return updateEntity(table, entity, new TableServiceOptions());
+ }
+
+ @Override
+ public UpdateEntityResult updateEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ return putOrMergeEntityCore(table, entity, "PUT", true/*includeEtag*/, options);
+ }
+
+ @Override
+ public UpdateEntityResult mergeEntity(String table, Entity entity) throws ServiceException {
+ return updateEntity(table, entity, new TableServiceOptions());
+ }
+
+ @Override
+ public UpdateEntityResult mergeEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ return putOrMergeEntityCore(table, entity, "MERGE", true/*includeEtag*/, options);
+ }
+
+ @Override
+ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity) throws ServiceException {
+ return insertOrReplaceEntity(table, entity, new TableServiceOptions());
+ }
+
+ @Override
+ public UpdateEntityResult insertOrReplaceEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ return putOrMergeEntityCore(table, entity, "PUT", false/*includeEtag*/, options);
+ }
+
+ @Override
+ public UpdateEntityResult insertOrMergeEntity(String table, Entity entity) throws ServiceException {
+ return insertOrReplaceEntity(table, entity, new TableServiceOptions());
+ }
+
+ @Override
+ public UpdateEntityResult insertOrMergeEntity(String table, Entity entity, TableServiceOptions options)
+ throws ServiceException {
+ return putOrMergeEntityCore(table, entity, "MERGE", false/*includeEtag*/, options);
+ }
+
+ private UpdateEntityResult putOrMergeEntityCore(String table, Entity entity, String verb, boolean includeEtag,
+ TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path(
+ getEntityPath(table, entity.getPartitionKey(), entity.getRowKey()));
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+ if (includeEtag) {
+ builder = addIfMatchHeader(builder, entity.getEtag());
+ }
+
+ builder = builder.entity(atomReaderWriter.generateEntityEntry(entity), "application/atom+xml");
+
+ ClientResponse response = builder.method(verb, ClientResponse.class);
+ ThrowIfError(response);
+
+ UpdateEntityResult result = new UpdateEntityResult();
+ result.setEtag(response.getHeaders().getFirst("ETag"));
+
+ return result;
+ }
+
+ @Override
+ public void deleteEntity(String table, String partitionKey, String rowKey) throws ServiceException {
+ deleteEntity(table, partitionKey, rowKey, new DeleteEntityOptions());
+ }
+
+ @Override
+ public void deleteEntity(String table, String partitionKey, String rowKey, DeleteEntityOptions options)
+ throws ServiceException {
+ WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey));
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+ builder = addIfMatchHeader(builder, options.getEtag());
+
+ ClientResponse response = builder.delete(ClientResponse.class);
+ ThrowIfError(response);
+ }
+
+ @Override
+ public GetEntityResult getEntity(String table, String partitionKey, String rowKey) throws ServiceException {
+ return getEntity(table, partitionKey, rowKey, new TableServiceOptions());
+ }
+
+ @Override
+ public GetEntityResult getEntity(String table, String partitionKey, String rowKey, TableServiceOptions options)
+ throws ServiceException {
+ WebResource webResource = getResource(options).path(getEntityPath(table, partitionKey, rowKey));
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ ClientResponse response = builder.get(ClientResponse.class);
+ ThrowIfError(response);
+
+ GetEntityResult result = new GetEntityResult();
+ result.setEntity(atomReaderWriter.parseEntityEntry(response.getEntityInputStream()));
+
+ return result;
+ }
+
+ @Override
+ public QueryEntitiesResult queryEntities(String table) throws ServiceException {
+ return queryEntities(table, new QueryEntitiesOptions());
+ }
+
+ @Override
+ public QueryEntitiesResult queryEntities(String table, QueryEntitiesOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path(table);
+ webResource = addOptionalQuery(webResource, options.getQuery());
+ webResource = addOptionalQueryParam(webResource, "NextPartitionKey",
+ encodeODataURIValue(options.getNextPartitionKey()));
+ webResource = addOptionalQueryParam(webResource, "NextRowKey", encodeODataURIValue(options.getNextRowKey()));
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ ClientResponse response = builder.get(ClientResponse.class);
+ ThrowIfError(response);
+
+ QueryEntitiesResult result = new QueryEntitiesResult();
+ result.setNextPartitionKey(response.getHeaders().getFirst("x-ms-continuation-NextPartitionKey"));
+ result.setNextRowKey(response.getHeaders().getFirst("x-ms-continuation-NextRowKey"));
+ result.setEntities(atomReaderWriter.parseEntityEntries(response.getEntityInputStream()));
+
+ return result;
+ }
+
+ @Override
+ public BatchResult batch(BatchOperations operations) throws ServiceException {
+ return batch(operations, new TableServiceOptions());
+ }
+
+ @Override
+ public BatchResult batch(BatchOperations operations, TableServiceOptions options) throws ServiceException {
+ WebResource webResource = getResource(options).path("$batch");
+
+ WebResource.Builder builder = webResource.getRequestBuilder();
+ builder = addTableRequestHeaders(builder);
+
+ MimeMultipart entity = createBatchRequestBody(operations);
+ builder = builder.type(entity.getContentType());
+
+ ClientResponse response = builder.post(ClientResponse.class, entity);
+ ThrowIfError(response);
+
+ BatchResult result = new BatchResult();
+ result.setEntries(parseBatchResponse(response, operations));
+
+ return result;
+ }
+
+ private MimeMultipart createBatchRequestBody(BatchOperations operations) {
+ List bodyPartContents = new ArrayList();
+ int contentId = 1;
+ for (Operation operation : operations.getOperations()) {
+
+ DataSource bodyPartContent = null;
+ if (operation instanceof InsertEntityOperation) {
+ InsertEntityOperation op = (InsertEntityOperation) operation;
+ bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "POST",
+ false/*includeEtag*/, contentId);
+ contentId++;
+ }
+ else if (operation instanceof UpdateEntityOperation) {
+ UpdateEntityOperation op = (UpdateEntityOperation) operation;
+ bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "PUT",
+ true/*includeEtag*/, contentId);
+ contentId++;
+ }
+ else if (operation instanceof MergeEntityOperation) {
+ MergeEntityOperation op = (MergeEntityOperation) operation;
+ bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "MERGE",
+ true/*includeEtag*/, contentId);
+ contentId++;
+ }
+ else if (operation instanceof InsertOrReplaceEntityOperation) {
+ InsertOrReplaceEntityOperation op = (InsertOrReplaceEntityOperation) operation;
+ bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "PUT",
+ false/*includeEtag*/, contentId);
+ contentId++;
+ }
+ else if (operation instanceof InsertOrMergeEntityOperation) {
+ InsertOrMergeEntityOperation op = (InsertOrMergeEntityOperation) operation;
+ bodyPartContent = createBatchInsertOrUpdateEntityPart(op.getTable(), op.getEntity(), "MERGE",
+ false/*includeEtag*/, contentId);
+ contentId++;
+ }
+ else if (operation instanceof DeleteEntityOperation) {
+ DeleteEntityOperation op = (DeleteEntityOperation) operation;
+ bodyPartContent = createBatchDeleteEntityPart(op.getTable(), op.getPartitionKey(), op.getRowKey(),
+ op.getEtag(), contentId);
+ contentId++;
+ }
+
+ if (bodyPartContent != null) {
+ bodyPartContents.add(bodyPartContent);
+ }
+ }
+
+ return mimeReaderWriter.getMimeMultipart(bodyPartContents);
+ }
+
+ private DataSource createBatchInsertOrUpdateEntityPart(String table, Entity entity, String verb,
+ boolean includeEtag, int contentId) {
+
+ URI path;
+ if ("POST".equals(verb)) {
+ path = channel.resource(url).path(table).getURI();
+ }
+ else {
+ path = channel.resource(url).path(getEntityPath(table, entity.getPartitionKey(), entity.getRowKey()))
+ .getURI();
+ }
+
+ //
+ // Stream content into byte[] so that we have the length
+ //
+ InputStream stream = atomReaderWriter.generateEntityEntry(entity);
+ byte[] bytes = inputStreamToByteArray(stream);
+
+ //
+ // Create body of MIME part as the HTTP request
+ //
+ InternetHeaders headers = new InternetHeaders();
+ headers.addHeader("Content-ID", Integer.toString(contentId));
+ headers.addHeader("Content-Type", "application/atom+xml;type=entry");
+ headers.addHeader("Content-Length", Integer.toString(bytes.length));
+ if (includeEtag) {
+ headers.addHeader("If-Match", entity.getEtag());
+ }
+
+ //TODO: Review code to make sure encoding is correct
+ ByteArrayOutputStream httpRequest = new ByteArrayOutputStream();
+ httpReaderWriter.appendMethod(httpRequest, verb, path);
+ httpReaderWriter.appendHeaders(httpRequest, headers);
+ httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(bytes));
+
+ DataSource bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()),
+ "application/http");
+ return bodyPartContent;
+ }
+
+ private DataSource createBatchDeleteEntityPart(String table, String partitionKey, String rowKey, String etag,
+ int contentId) {
+
+ URI path = channel.resource(url).path(getEntityPath(table, partitionKey, rowKey)).getURI();
+
+ //
+ // Create body of MIME part as the HTTP request
+ //
+ InternetHeaders headers = new InternetHeaders();
+ headers.addHeader("Content-ID", Integer.toString(contentId));
+ headers.addHeader("If-Match", etag == null ? "*" : etag);
+
+ //TODO: Review code to make sure encoding is correct
+ ByteArrayOutputStream httpRequest = new ByteArrayOutputStream();
+ httpReaderWriter.appendMethod(httpRequest, "DELETE", path);
+ httpReaderWriter.appendHeaders(httpRequest, headers);
+ httpReaderWriter.appendEntity(httpRequest, new ByteArrayInputStream(new byte[0]));
+
+ DataSource bodyPartContent = new InputStreamDataSource(new ByteArrayInputStream(httpRequest.toByteArray()),
+ "application/http");
+ return bodyPartContent;
+ }
+
+ private List parseBatchResponse(ClientResponse response, BatchOperations operations) {
+ List parts = mimeReaderWriter.parseParts(response.getEntityInputStream(), response.getHeaders()
+ .getFirst("Content-Type"));
+
+ if (parts.size() != operations.getOperations().size()) {
+ throw new UniformInterfaceException(String.format(
+ "Batch response from server does not contain the correct amount "
+ + "of parts (expecting %d, received %d instead)", parts.size(), operations.getOperations()
+ .size()), response);
+ }
+
+ List result = new ArrayList();
+ for (int i = 0; i < parts.size(); i++) {
+ DataSource ds = parts.get(i);
+ Operation operation = operations.getOperations().get(i);
+
+ StatusLine status = httpReaderWriter.parseStatusLine(ds);
+ InternetHeaders headers = httpReaderWriter.parseHeaders(ds);
+ InputStream content = httpReaderWriter.parseEntity(ds);
+
+ if (status.getStatus() >= 400) {
+ // Create dummy client response with status, headers and content
+ InBoundHeaders inBoundHeaders = new InBoundHeaders();
+
+ Enumeration e = headers.getAllHeaders();
+ while (e.hasMoreElements()) {
+ Header header = e.nextElement();
+ inBoundHeaders.putSingle(header.getName(), header.getValue());
+ }
+
+ ClientResponse dummyResponse = new ClientResponse(status.getStatus(), inBoundHeaders, content, null);
+
+ // Wrap into a ServiceException
+ UniformInterfaceException exception = new UniformInterfaceException(dummyResponse);
+ ServiceException serviceException = new ServiceException(exception);
+ serviceException = ServiceExceptionFactory.process("table", serviceException);
+ Error error = new Error().setError(serviceException);
+
+ result.add(error);
+ }
+ else if (operation instanceof InsertEntityOperation) {
+ InsertEntity opResult = new InsertEntity().setEntity(atomReaderWriter.parseEntityEntry(content));
+ result.add(opResult);
+ }
+ else if ((operation instanceof UpdateEntityOperation) || (operation instanceof MergeEntityOperation)
+ || (operation instanceof InsertOrReplaceEntityOperation)
+ || (operation instanceof InsertOrMergeEntityOperation)) {
+ UpdateEntity opResult = new UpdateEntity().setEtag(headers.getHeader("ETag", null));
+ result.add(opResult);
+ }
+ else if (operation instanceof DeleteEntityOperation) {
+ DeleteEntity opResult = new DeleteEntity();
+ result.add(opResult);
+ }
+ }
+
+ return result;
+ }
+
+ private byte[] inputStreamToByteArray(InputStream inputStream) {
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+ byte[] buffer = new byte[1024];
+ try {
+ while (true) {
+ int n = inputStream.read(buffer);
+ if (n == -1)
+ break;
+ outputStream.write(buffer, 0, n);
+ }
+ }
+ finally {
+ inputStream.close();
+ }
+ return outputStream.toByteArray();
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java
new file mode 100644
index 000000000000..0e97c0193ab5
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/implementation/XMLStreamFactory.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.implementation;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamWriter;
+
+public interface XMLStreamFactory {
+ XMLStreamWriter getWriter(OutputStream stream);
+
+ XMLStreamReader getReader(InputStream stream);
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java
new file mode 100644
index 000000000000..f1df873ac4da
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchOperations.java
@@ -0,0 +1,208 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BatchOperations {
+ private List operations = new ArrayList();
+
+ public List getOperations() {
+ return operations;
+ }
+
+ public void setOperations(List operations) {
+ this.operations = operations;
+ }
+
+ public BatchOperations addInsertEntity(String table, Entity entity) {
+ this.operations.add(new InsertEntityOperation().setTable(table).setEntity(entity));
+ return this;
+ }
+
+ public BatchOperations addUpdateEntity(String table, Entity entity) {
+ this.operations.add(new UpdateEntityOperation().setTable(table).setEntity(entity));
+ return this;
+ }
+
+ public BatchOperations addMergeEntity(String table, Entity entity) {
+ this.operations.add(new MergeEntityOperation().setTable(table).setEntity(entity));
+ return this;
+ }
+
+ public BatchOperations addInsertOrReplaceEntity(String table, Entity entity) {
+ this.operations.add(new InsertOrReplaceEntityOperation().setTable(table).setEntity(entity));
+ return this;
+ }
+
+ public BatchOperations addInsertOrMergeEntity(String table, Entity entity) {
+ this.operations.add(new InsertOrMergeEntityOperation().setTable(table).setEntity(entity));
+ return this;
+ }
+
+ public BatchOperations addDeleteEntity(String table, String partitionKey, String rowKey, String etag) {
+ this.operations.add(new DeleteEntityOperation().setTable(table).setPartitionKey(partitionKey).setRowKey(rowKey)
+ .setEtag(etag));
+ return this;
+ }
+
+ public static abstract class Operation {
+ }
+
+ public static class InsertEntityOperation extends Operation {
+ private String table;
+ private Entity entity;
+
+ public String getTable() {
+ return table;
+ }
+
+ public InsertEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public InsertEntityOperation setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class UpdateEntityOperation extends Operation {
+ private String table;
+ private Entity entity;
+
+ public String getTable() {
+ return table;
+ }
+
+ public UpdateEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public UpdateEntityOperation setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class MergeEntityOperation extends Operation {
+ private String table;
+ private Entity entity;
+
+ public String getTable() {
+ return table;
+ }
+
+ public MergeEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public MergeEntityOperation setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class InsertOrReplaceEntityOperation extends Operation {
+ private String table;
+ private Entity entity;
+
+ public String getTable() {
+ return table;
+ }
+
+ public InsertOrReplaceEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public InsertOrReplaceEntityOperation setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class InsertOrMergeEntityOperation extends Operation {
+ private String table;
+ private Entity entity;
+
+ public String getTable() {
+ return table;
+ }
+
+ public InsertOrMergeEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public InsertOrMergeEntityOperation setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class DeleteEntityOperation extends Operation {
+ private String table;
+ private String partitionKey;
+ private String rowKey;
+ private String etag;
+
+ public String getTable() {
+ return table;
+ }
+
+ public DeleteEntityOperation setTable(String table) {
+ this.table = table;
+ return this;
+ }
+
+ public String getPartitionKey() {
+ return partitionKey;
+ }
+
+ public DeleteEntityOperation setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ return this;
+ }
+
+ public String getRowKey() {
+ return rowKey;
+ }
+
+ public DeleteEntityOperation setRowKey(String rowKey) {
+ this.rowKey = rowKey;
+ return this;
+ }
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public DeleteEntityOperation setEtag(String etag) {
+ this.etag = etag;
+ return this;
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java
new file mode 100644
index 000000000000..30a0b3beebd9
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BatchResult.java
@@ -0,0 +1,65 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.microsoft.windowsazure.services.core.ServiceException;
+
+public class BatchResult {
+ private List entries = new ArrayList();
+
+ public List getEntries() {
+ return entries;
+ }
+
+ public BatchResult setEntries(List entries) {
+ this.entries = entries;
+ return this;
+ }
+
+ public static abstract class Entry {
+ }
+
+ public static class InsertEntity extends Entry {
+ private Entity entity;
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public InsertEntity setEntity(Entity entity) {
+ this.entity = entity;
+ return this;
+ }
+ }
+
+ public static class UpdateEntity extends Entry {
+ private String etag;
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public UpdateEntity setEtag(String etag) {
+ this.etag = etag;
+ return this;
+ }
+ }
+
+ public static class DeleteEntity extends Entry {
+
+ }
+
+ public static class Error extends Entry {
+ private ServiceException error;
+
+ public ServiceException getError() {
+ return error;
+ }
+
+ public Error setError(ServiceException error) {
+ this.error = error;
+ return this;
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java
new file mode 100644
index 000000000000..462be9a865c6
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/BinaryFilter.java
@@ -0,0 +1,34 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class BinaryFilter extends Filter {
+ private String operator;
+ private Filter left;
+ private Filter right;
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public BinaryFilter setOperator(String operator) {
+ this.operator = operator;
+ return this;
+ }
+
+ public Filter getLeft() {
+ return left;
+ }
+
+ public BinaryFilter setLeft(Filter left) {
+ this.left = left;
+ return this;
+ }
+
+ public Filter getRight() {
+ return right;
+ }
+
+ public BinaryFilter setRight(Filter right) {
+ this.right = right;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java
new file mode 100644
index 000000000000..9dd16b1a1daa
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ConstantFilter.java
@@ -0,0 +1,14 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class ConstantFilter extends Filter {
+ private Object value;
+
+ public Object getValue() {
+ return value;
+ }
+
+ public ConstantFilter setValue(Object value) {
+ this.value = value;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java
new file mode 100644
index 000000000000..441f3020822b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/DeleteEntityOptions.java
@@ -0,0 +1,21 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class DeleteEntityOptions extends TableServiceOptions {
+ private String etag;
+
+ @Override
+ public DeleteEntityOptions setTimeout(Integer timeout) {
+ super.setTimeout(timeout);
+ return this;
+ }
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public DeleteEntityOptions setEtag(String etag) {
+ this.etag = etag;
+ return this;
+ }
+
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java
new file mode 100644
index 000000000000..5a82435b9337
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/EdmType.java
@@ -0,0 +1,12 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class EdmType {
+ public static final String DATETIME = "Edm.DateTime";
+ public static final String BINARY = "Edm.Binary";
+ public static final String BOOLEAN = "Edm.Boolean";
+ public static final String DOUBLE = "Edm.Double";
+ public static final String GUID = "Edm.Guid";
+ public static final String INT32 = "Edm.Int32";
+ public static final String INT64 = "Edm.Int64";
+ public static final String STRING = "Edm.String";
+}
\ No newline at end of file
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java
new file mode 100644
index 000000000000..ce1d6dff3933
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Entity.java
@@ -0,0 +1,77 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Entity {
+ private String etag;
+ private Map properties = new HashMap();
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public Entity setEtag(String etag) {
+ this.etag = etag;
+ return this;
+ }
+
+ public String getPartitionKey() {
+ Property p = getProperty("PartitionKey");
+ return p == null ? null : (String) p.getValue();
+ }
+
+ public Entity setPartitionKey(String partitionKey) {
+ setProperty("PartitionKey", null, partitionKey);
+ return this;
+ }
+
+ public String getRowKey() {
+ Property p = getProperty("RowKey");
+ return p == null ? null : (String) p.getValue();
+ }
+
+ public Entity setRowKey(String rowKey) {
+ setProperty("RowKey", null, rowKey);
+ return this;
+ }
+
+ public Date getTimestamp() {
+ Property p = getProperty("Timestamp");
+ return p == null ? null : (Date) p.getValue();
+ }
+
+ public Entity setTimestamp(Date timestamp) {
+ setProperty("Timestamp", null, timestamp);
+ return this;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ public Entity setProperties(Map properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public Property getProperty(String name) {
+ return properties.get(name);
+ }
+
+ public Entity setProperty(String name, Property property) {
+ this.properties.put(name, property);
+ return this;
+ }
+
+ public Entity setProperty(String name, String edmType, Object value) {
+ setProperty(name, new Property().setEdmType(edmType).setValue(value));
+ return this;
+ }
+
+ public Object getPropertyValue(String name) {
+ Property p = getProperty(name);
+ return p == null ? null : p.getValue();
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java
new file mode 100644
index 000000000000..f0a54f58b09b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Filter.java
@@ -0,0 +1,51 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class Filter {
+ public static UnaryFilter not(Filter operand) {
+ return new UnaryFilter().setOperator("not").setOperand(operand);
+ }
+
+ public static BinaryFilter and(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("and").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter or(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("or").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter eq(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("eq").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter ne(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("ne").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter ge(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("ge").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter gt(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("gt").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter lt(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("lt").setLeft(left).setRight(right);
+ }
+
+ public static BinaryFilter le(Filter left, Filter right) {
+ return new BinaryFilter().setOperator("le").setLeft(left).setRight(right);
+ }
+
+ public static ConstantFilter constant(Object value) {
+ return new ConstantFilter().setValue(value);
+ }
+
+ public static LitteralFilter litteral(String value) {
+ return new LitteralFilter().setLitteral(value);
+ }
+
+ public static RawStringFilter rawString(String value) {
+ return new RawStringFilter().setRawString(value);
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java
new file mode 100644
index 000000000000..34195d479504
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetEntityResult.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class GetEntityResult {
+ private Entity entity;
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public void setEntity(Entity entity) {
+ this.entity = entity;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java
new file mode 100644
index 000000000000..30ab7196d448
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetServicePropertiesResult.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class GetServicePropertiesResult {
+ private ServiceProperties value;
+
+ public ServiceProperties getValue() {
+ return value;
+ }
+
+ public void setValue(ServiceProperties value) {
+ this.value = value;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java
new file mode 100644
index 000000000000..de199b24f075
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/GetTableResult.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class GetTableResult {
+ private TableEntry tableEntry;
+
+ public TableEntry getTableEntry() {
+ return tableEntry;
+ }
+
+ public void setTableEntry(TableEntry tableEntry) {
+ this.tableEntry = tableEntry;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java
new file mode 100644
index 000000000000..666fc9634da3
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/InsertEntityResult.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class InsertEntityResult {
+ private Entity entity;
+
+ public Entity getEntity() {
+ return entity;
+ }
+
+ public void setEntity(Entity entity) {
+ this.entity = entity;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java
new file mode 100644
index 000000000000..25f042b564f0
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ListTablesOptions.java
@@ -0,0 +1,20 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class ListTablesOptions extends TableServiceOptions {
+ private String prefix;
+
+ @Override
+ public ListTablesOptions setTimeout(Integer timeout) {
+ super.setTimeout(timeout);
+ return this;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public ListTablesOptions setPrefix(String prefix) {
+ this.prefix = prefix;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java
new file mode 100644
index 000000000000..d67f47162c4d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/LitteralFilter.java
@@ -0,0 +1,14 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class LitteralFilter extends Filter {
+ private String litteral;
+
+ public String getLitteral() {
+ return litteral;
+ }
+
+ public LitteralFilter setLitteral(String litteral) {
+ this.litteral = litteral;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java
new file mode 100644
index 000000000000..1e90530e04f0
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Property.java
@@ -0,0 +1,24 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class Property {
+ private String edmType;
+ private Object value;
+
+ public String getEdmType() {
+ return edmType;
+ }
+
+ public Property setEdmType(String edmType) {
+ this.edmType = edmType;
+ return this;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Property setValue(Object value) {
+ this.value = value;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java
new file mode 100644
index 000000000000..57e7f8c30b66
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/Query.java
@@ -0,0 +1,67 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Query {
+ private List selectFields = new ArrayList();
+ private String from;
+ private Filter filter;
+ private List orderByFields = new ArrayList();
+ private Integer top;
+
+ public List getSelectFields() {
+ return selectFields;
+ }
+
+ public Query setSelectFields(List selectFields) {
+ this.selectFields = selectFields;
+ return this;
+ }
+
+ public Query addSelectField(String selectField) {
+ this.selectFields.add(selectField);
+ return this;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public Query setFrom(String from) {
+ this.from = from;
+ return this;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public Query setFilter(Filter filter) {
+ this.filter = filter;
+ return this;
+ }
+
+ public List getOrderByFields() {
+ return orderByFields;
+ }
+
+ public Query setOrderByFields(List orderByFields) {
+ this.orderByFields = orderByFields;
+ return this;
+ }
+
+ public Query addOrderByField(String orderByField) {
+ this.orderByFields.add(orderByField);
+ return this;
+ }
+
+ public Integer getTop() {
+ return top;
+ }
+
+ public Query setTop(Integer top) {
+ this.top = top;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java
new file mode 100644
index 000000000000..1de1cd47426b
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesOptions.java
@@ -0,0 +1,40 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class QueryEntitiesOptions extends TableServiceOptions {
+ private Query query;
+ public String nextPartitionKey;
+ public String nextRowKey;
+
+ @Override
+ public QueryEntitiesOptions setTimeout(Integer timeout) {
+ super.setTimeout(timeout);
+ return this;
+ }
+
+ public Query getQuery() {
+ return query;
+ }
+
+ public QueryEntitiesOptions setQuery(Query query) {
+ this.query = query;
+ return this;
+ }
+
+ public String getNextPartitionKey() {
+ return nextPartitionKey;
+ }
+
+ public QueryEntitiesOptions setNextPartitionKey(String nextPartitionKey) {
+ this.nextPartitionKey = nextPartitionKey;
+ return this;
+ }
+
+ public String getNextRowKey() {
+ return nextRowKey;
+ }
+
+ public QueryEntitiesOptions setNextRowKey(String nextRowKey) {
+ this.nextRowKey = nextRowKey;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java
new file mode 100644
index 000000000000..3977ccc9b97d
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryEntitiesResult.java
@@ -0,0 +1,34 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QueryEntitiesResult {
+ private String nextPartitionKey;
+ private String nextRowKey;
+ private List entities = new ArrayList();
+
+ public List getEntities() {
+ return entities;
+ }
+
+ public void setEntities(List entities) {
+ this.entities = entities;
+ }
+
+ public String getNextPartitionKey() {
+ return nextPartitionKey;
+ }
+
+ public void setNextPartitionKey(String nextPartitionKey) {
+ this.nextPartitionKey = nextPartitionKey;
+ }
+
+ public String getNextRowKey() {
+ return nextRowKey;
+ }
+
+ public void setNextRowKey(String nextRowKey) {
+ this.nextRowKey = nextRowKey;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java
new file mode 100644
index 000000000000..d402cd8527cc
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesOptions.java
@@ -0,0 +1,30 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class QueryTablesOptions extends TableServiceOptions {
+ private String nextTableName;
+ private Query query;
+
+ @Override
+ public QueryTablesOptions setTimeout(Integer timeout) {
+ super.setTimeout(timeout);
+ return this;
+ }
+
+ public Query getQuery() {
+ return query;
+ }
+
+ public QueryTablesOptions setQuery(Query query) {
+ this.query = query;
+ return this;
+ }
+
+ public String getNextTableName() {
+ return nextTableName;
+ }
+
+ public QueryTablesOptions setNextTableName(String nextTableName) {
+ this.nextTableName = nextTableName;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java
new file mode 100644
index 000000000000..dfd1c1b7f4aa
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/QueryTablesResult.java
@@ -0,0 +1,24 @@
+package com.microsoft.windowsazure.services.table.models;
+
+import java.util.List;
+
+public class QueryTablesResult {
+ private String nextTableName;
+ private List tables;
+
+ public String getNextTableName() {
+ return nextTableName;
+ }
+
+ public void setNextTableName(String nextTableName) {
+ this.nextTableName = nextTableName;
+ }
+
+ public List getTables() {
+ return tables;
+ }
+
+ public void setTables(List tables) {
+ this.tables = tables;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java
new file mode 100644
index 000000000000..ffc8e38aef00
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/RawStringFilter.java
@@ -0,0 +1,14 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class RawStringFilter extends Filter {
+ private String rawString;
+
+ public String getRawString() {
+ return rawString;
+ }
+
+ public RawStringFilter setRawString(String rawString) {
+ this.rawString = rawString;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java
new file mode 100644
index 000000000000..2e068e4fe9df
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/ServiceProperties.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "StorageServiceProperties")
+public class ServiceProperties {
+ private Logging logging = new Logging();
+ private Metrics metrics = new Metrics();
+
+ @XmlElement(name = "Logging")
+ public Logging getLogging() {
+ return logging;
+ }
+
+ public void setLogging(Logging logging) {
+ this.logging = logging;
+ }
+
+ @XmlElement(name = "Metrics")
+ public Metrics getMetrics() {
+ return metrics;
+ }
+
+ public void setMetrics(Metrics metrics) {
+ this.metrics = metrics;
+ }
+
+ public static class Logging {
+ private String version;
+ private Boolean delete;
+ private Boolean read;
+ private Boolean write;
+ private RetentionPolicy retentionPolicy;
+
+ @XmlElement(name = "RetentionPolicy")
+ public RetentionPolicy getRetentionPolicy() {
+ return retentionPolicy;
+ }
+
+ public void setRetentionPolicy(RetentionPolicy retentionPolicy) {
+ this.retentionPolicy = retentionPolicy;
+ }
+
+ @XmlElement(name = "Write")
+ public boolean isWrite() {
+ return write;
+ }
+
+ public void setWrite(boolean write) {
+ this.write = write;
+ }
+
+ @XmlElement(name = "Read")
+ public boolean isRead() {
+ return read;
+ }
+
+ public void setRead(boolean read) {
+ this.read = read;
+ }
+
+ @XmlElement(name = "Delete")
+ public boolean isDelete() {
+ return delete;
+ }
+
+ public void setDelete(boolean delete) {
+ this.delete = delete;
+ }
+
+ @XmlElement(name = "Version")
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+ }
+
+ public static class Metrics {
+ private String version;
+ private boolean enabled;
+ private Boolean includeAPIs;
+ private RetentionPolicy retentionPolicy;
+
+ @XmlElement(name = "RetentionPolicy")
+ public RetentionPolicy getRetentionPolicy() {
+ return retentionPolicy;
+ }
+
+ public void setRetentionPolicy(RetentionPolicy retentionPolicy) {
+ this.retentionPolicy = retentionPolicy;
+ }
+
+ @XmlElement(name = "IncludeAPIs")
+ public Boolean isIncludeAPIs() {
+ return includeAPIs;
+ }
+
+ public void setIncludeAPIs(Boolean includeAPIs) {
+ this.includeAPIs = includeAPIs;
+ }
+
+ @XmlElement(name = "Enabled")
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ @XmlElement(name = "Version")
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+ }
+
+ public static class RetentionPolicy {
+ private boolean enabled;
+ private Integer days; // nullable, because optional if "enabled" is false
+
+ @XmlElement(name = "Days")
+ public Integer getDays() {
+ return days;
+ }
+
+ public void setDays(Integer days) {
+ this.days = days;
+ }
+
+ @XmlElement(name = "Enabled")
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java
new file mode 100644
index 000000000000..917cecc3409c
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableEntry.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class TableEntry {
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java
new file mode 100644
index 000000000000..e42240885c15
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/TableServiceOptions.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.models;
+
+public class TableServiceOptions {
+ // Nullable because it is optional
+ private Integer timeout;
+
+ public Integer getTimeout() {
+ return timeout;
+ }
+
+ public TableServiceOptions setTimeout(Integer timeout) {
+ this.timeout = timeout;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java
new file mode 100644
index 000000000000..42d4b7a870fc
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UnaryFilter.java
@@ -0,0 +1,24 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class UnaryFilter extends Filter {
+ private String operator;
+ private Filter operand;
+
+ public String getOperator() {
+ return operator;
+ }
+
+ public UnaryFilter setOperator(String operator) {
+ this.operator = operator;
+ return this;
+ }
+
+ public Filter getOperand() {
+ return operand;
+ }
+
+ public UnaryFilter setOperand(Filter operand) {
+ this.operand = operand;
+ return this;
+ }
+}
diff --git a/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java
new file mode 100644
index 000000000000..2db782e63693
--- /dev/null
+++ b/microsoft-azure-api/src/main/java/com/microsoft/windowsazure/services/table/models/UpdateEntityResult.java
@@ -0,0 +1,13 @@
+package com.microsoft.windowsazure.services.table.models;
+
+public class UpdateEntityResult {
+ private String etag;
+
+ public String getEtag() {
+ return etag;
+ }
+
+ public void setEtag(String etag) {
+ this.etag = etag;
+ }
+}
diff --git a/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports b/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports
index cc7087393981..07fdb1c4e7b6 100644
--- a/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports
+++ b/microsoft-azure-api/src/main/resources/META-INF/services/com.microsoft.windowsazure.services.core.Builder$Exports
@@ -1,5 +1,6 @@
com.microsoft.windowsazure.services.blob.Exports
com.microsoft.windowsazure.services.queue.Exports
+com.microsoft.windowsazure.services.table.Exports
com.microsoft.windowsazure.services.serviceBus.Exports
com.microsoft.windowsazure.services.serviceBus.implementation.Exports
com.microsoft.windowsazure.services.core.utils.Exports
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java
new file mode 100644
index 000000000000..6f514dad26d3
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/blob/implementation/ISO8601DateConverterTests.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.blob.implementation;
+
+import static org.junit.Assert.*;
+
+import java.util.Date;
+
+import org.junit.Test;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+
+public class ISO8601DateConverterTests {
+ @Test
+ public void shortFormatWorks() throws Exception {
+ // Arrange
+ ISO8601DateConverter converter = new ISO8601DateConverter();
+ String value = "2012-01-12T00:35:58Z";
+
+ // Act
+ Date result = converter.parse(value);
+
+ // Assert
+ assertNotNull(result);
+ }
+
+ @Test
+ public void longFormatWorks() throws Exception {
+ // Arrange
+ ISO8601DateConverter converter = new ISO8601DateConverter();
+ String value = "2012-01-12T00:35:58.1234567Z";
+
+ // Act
+ Date result = converter.parse(value);
+
+ // Assert
+ assertNotNull(result);
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java
new file mode 100644
index 000000000000..8b7b18382d59
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/IntegrationTestBase.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import com.microsoft.windowsazure.services.core.Configuration;
+
+public abstract class IntegrationTestBase {
+ protected static Configuration createConfiguration() {
+ Configuration config = Configuration.getInstance();
+ overrideWithEnv(config, TableConfiguration.ACCOUNT_NAME);
+ overrideWithEnv(config, TableConfiguration.ACCOUNT_KEY);
+ overrideWithEnv(config, TableConfiguration.URI);
+ return config;
+ }
+
+ private static void overrideWithEnv(Configuration config, String key) {
+ String value = System.getenv(key);
+ if (value == null)
+ return;
+
+ config.setProperty(key, value);
+ }
+
+ protected static boolean isRunningWithEmulator(Configuration config) {
+ String accountName = "devstoreaccount1";
+ String accountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==";
+
+ return accountName.equals(config.getProperty(TableConfiguration.ACCOUNT_NAME))
+ && accountKey.equals(config.getProperty(TableConfiguration.ACCOUNT_KEY));
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java
new file mode 100644
index 000000000000..c8143349741d
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/TableServiceIntegrationTest.java
@@ -0,0 +1,913 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table;
+
+import static org.junit.Assert.*;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.microsoft.windowsazure.services.core.Configuration;
+import com.microsoft.windowsazure.services.core.ExponentialRetryPolicy;
+import com.microsoft.windowsazure.services.core.RetryPolicyFilter;
+import com.microsoft.windowsazure.services.core.ServiceException;
+import com.microsoft.windowsazure.services.table.models.BatchOperations;
+import com.microsoft.windowsazure.services.table.models.BatchResult;
+import com.microsoft.windowsazure.services.table.models.BatchResult.DeleteEntity;
+import com.microsoft.windowsazure.services.table.models.BatchResult.InsertEntity;
+import com.microsoft.windowsazure.services.table.models.BatchResult.UpdateEntity;
+import com.microsoft.windowsazure.services.table.models.DeleteEntityOptions;
+import com.microsoft.windowsazure.services.table.models.EdmType;
+import com.microsoft.windowsazure.services.table.models.Entity;
+import com.microsoft.windowsazure.services.table.models.Filter;
+import com.microsoft.windowsazure.services.table.models.GetEntityResult;
+import com.microsoft.windowsazure.services.table.models.GetTableResult;
+import com.microsoft.windowsazure.services.table.models.InsertEntityResult;
+import com.microsoft.windowsazure.services.table.models.ListTablesOptions;
+import com.microsoft.windowsazure.services.table.models.Query;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesOptions;
+import com.microsoft.windowsazure.services.table.models.QueryEntitiesResult;
+import com.microsoft.windowsazure.services.table.models.QueryTablesResult;
+import com.microsoft.windowsazure.services.table.models.ServiceProperties;
+import com.microsoft.windowsazure.services.table.models.TableEntry;
+
+public class TableServiceIntegrationTest extends IntegrationTestBase {
+ private static final String testTablesPrefix = "sdktest";
+ private static final String createableTablesPrefix = "csdktest";
+ private static String TEST_TABLE_1;
+ private static String TEST_TABLE_2;
+ private static String TEST_TABLE_3;
+ private static String TEST_TABLE_4;
+ private static String TEST_TABLE_5;
+ private static String TEST_TABLE_6;
+ private static String TEST_TABLE_7;
+ private static String TEST_TABLE_8;
+ private static String CREATABLE_TABLE_1;
+ private static String CREATABLE_TABLE_2;
+ //private static String CREATABLE_TABLE_3;
+ private static String[] creatableTables;
+ private static String[] testTables;
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ //System.setProperty("http.proxyHost", "127.0.0.1");
+ //System.setProperty("http.proxyPort", "8888");
+
+ // Setup container names array (list of container names used by
+ // integration tests)
+ testTables = new String[10];
+ for (int i = 0; i < testTables.length; i++) {
+ testTables[i] = String.format("%s%d", testTablesPrefix, i + 1);
+ }
+
+ creatableTables = new String[10];
+ for (int i = 0; i < creatableTables.length; i++) {
+ creatableTables[i] = String.format("%s%d", createableTablesPrefix, i + 1);
+ }
+
+ TEST_TABLE_1 = testTables[0];
+ TEST_TABLE_2 = testTables[1];
+ TEST_TABLE_3 = testTables[2];
+ TEST_TABLE_4 = testTables[3];
+ TEST_TABLE_5 = testTables[4];
+ TEST_TABLE_6 = testTables[5];
+ TEST_TABLE_7 = testTables[6];
+ TEST_TABLE_8 = testTables[7];
+
+ CREATABLE_TABLE_1 = creatableTables[0];
+ CREATABLE_TABLE_2 = creatableTables[1];
+ //CREATABLE_TABLE_3 = creatableTables[2];
+
+ // Create all test containers and their content
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ deleteAllTables(service, testTables);
+ deleteAllTables(service, creatableTables);
+ createTables(service, testTablesPrefix, testTables);
+ }
+
+ @AfterClass
+ public static void cleanup() throws Exception {
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ deleteTables(service, testTablesPrefix, testTables);
+ deleteTables(service, createableTablesPrefix, creatableTables);
+ }
+
+ private static void createTables(TableContract service, String prefix, String[] list) throws Exception {
+ // Retry creating every table as long as we get "409 - Table being deleted" error
+ service = service.withFilter(new RetryPolicyFilter(new ExponentialRetryPolicy(new int[] { 409 })));
+
+ Set containers = listTables(service, prefix);
+ for (String item : list) {
+ if (!containers.contains(item)) {
+ service.createTable(item);
+ }
+ }
+ }
+
+ private static void deleteTables(TableContract service, String prefix, String[] list) throws Exception {
+ Set containers = listTables(service, prefix);
+ for (String item : list) {
+ if (containers.contains(item)) {
+ service.deleteTable(item);
+ }
+ }
+ }
+
+ private static void deleteAllTables(TableContract service, String[] list) throws Exception {
+ for (String item : list) {
+ try {
+ service.deleteTable(item);
+ }
+ catch (ServiceException e) {
+ // Ignore
+ }
+ }
+ }
+
+ private static Set listTables(TableContract service, String prefix) throws Exception {
+ HashSet result = new HashSet();
+ QueryTablesResult list = service.listTables(new ListTablesOptions().setPrefix(prefix));
+ for (TableEntry item : list.getTables()) {
+ result.add(item.getName());
+ }
+ return result;
+ }
+
+ @Test
+ public void getServicePropertiesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Don't run this test with emulator, as v1.6 doesn't support this method
+ if (isRunningWithEmulator(config)) {
+ return;
+ }
+
+ // Act
+ ServiceProperties props = service.getServiceProperties().getValue();
+
+ // Assert
+ assertNotNull(props);
+ assertNotNull(props.getLogging());
+ assertNotNull(props.getLogging().getRetentionPolicy());
+ assertNotNull(props.getLogging().getVersion());
+ assertNotNull(props.getMetrics().getRetentionPolicy());
+ assertNotNull(props.getMetrics().getVersion());
+ }
+
+ @Test
+ public void setServicePropertiesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Don't run this test with emulator, as v1.6 doesn't support this method
+ if (isRunningWithEmulator(config)) {
+ return;
+ }
+
+ // Act
+ ServiceProperties props = service.getServiceProperties().getValue();
+
+ props.getLogging().setRead(true);
+ service.setServiceProperties(props);
+
+ props = service.getServiceProperties().getValue();
+
+ // Assert
+ assertNotNull(props);
+ assertNotNull(props.getLogging());
+ assertNotNull(props.getLogging().getRetentionPolicy());
+ assertNotNull(props.getLogging().getVersion());
+ assertTrue(props.getLogging().isRead());
+ assertNotNull(props.getMetrics().getRetentionPolicy());
+ assertNotNull(props.getMetrics().getVersion());
+ }
+
+ @Test
+ public void createTablesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ Exception error;
+ try {
+ service.getTable(CREATABLE_TABLE_1);
+ error = null;
+ }
+ catch (Exception e) {
+ error = e;
+ }
+ service.createTable(CREATABLE_TABLE_1);
+ GetTableResult result = service.getTable(CREATABLE_TABLE_1);
+
+ // Assert
+ assertNotNull(error);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void deleteTablesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ service.createTable(CREATABLE_TABLE_2);
+ GetTableResult result = service.getTable(CREATABLE_TABLE_2);
+
+ service.deleteTable(CREATABLE_TABLE_2);
+ Exception error;
+ try {
+ service.getTable(CREATABLE_TABLE_2);
+ error = null;
+ }
+ catch (Exception e) {
+ error = e;
+ }
+
+ // Assert
+ assertNotNull(error);
+ assertNotNull(result);
+ }
+
+ @Test
+ public void queryTablesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ QueryTablesResult result = service.queryTables();
+
+ // Assert
+ assertNotNull(result);
+ }
+
+ @Test
+ public void listTablesWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ QueryTablesResult result = service.listTables();
+
+ // Assert
+ assertNotNull(result);
+ }
+
+ @Test
+ public void queryTablesWithPrefixWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ QueryTablesResult result = service.listTables(new ListTablesOptions().setPrefix(testTablesPrefix));
+
+ // Assert
+ assertNotNull(result);
+ }
+
+ @Test
+ public void getTableWorks() throws Exception {
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+
+ // Act
+ GetTableResult result = service.getTable(TEST_TABLE_1);
+
+ // Assert
+ assertNotNull(result);
+ }
+
+ @Test
+ public void insertEntityWorks() throws Exception {
+ System.out.println("insertEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("insertEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity);
+
+ // Assert
+ assertNotNull(result);
+ assertNotNull(result.getEntity());
+
+ assertEquals("001", result.getEntity().getPartitionKey());
+ assertEquals("insertEntityWorks", result.getEntity().getRowKey());
+ assertNotNull(result.getEntity().getTimestamp());
+ assertNotNull(result.getEntity().getEtag());
+
+ assertNotNull(result.getEntity().getProperty("test"));
+ assertEquals(true, result.getEntity().getProperty("test").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test2"));
+ assertEquals("value", result.getEntity().getProperty("test2").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test3"));
+ assertEquals(3, result.getEntity().getProperty("test3").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test4"));
+ assertEquals(12345678901L, result.getEntity().getProperty("test4").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test5"));
+ assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date);
+ }
+
+ @Test
+ public void updateEntityWorks() throws Exception {
+ System.out.println("updateEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("updateEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity);
+ result.getEntity().setProperty("test4", EdmType.INT32, 5);
+ service.updateEntity(TEST_TABLE_2, result.getEntity());
+
+ // Assert
+ }
+
+ @Test
+ public void insertOrReplaceEntityWorks() throws Exception {
+ System.out.println("insertOrReplaceEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrReplaceEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ service.insertOrReplaceEntity(TEST_TABLE_2, entity);
+ entity.setProperty("test4", EdmType.INT32, 5);
+ entity.setProperty("test6", EdmType.INT32, 6);
+ service.insertOrReplaceEntity(TEST_TABLE_2, entity);
+
+ // Assert
+ }
+
+ @Test
+ public void insertOrMergeEntityWorks() throws Exception {
+ System.out.println("insertOrMergeEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("insertOrMergeEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ service.insertOrMergeEntity(TEST_TABLE_2, entity);
+ entity.setProperty("test4", EdmType.INT32, 5);
+ entity.setProperty("test6", EdmType.INT32, 6);
+ service.insertOrMergeEntity(TEST_TABLE_2, entity);
+
+ // Assert
+ }
+
+ @Test
+ public void mergeEntityWorks() throws Exception {
+ System.out.println("mergeEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("mergeEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity);
+
+ result.getEntity().setProperty("test4", EdmType.INT32, 5);
+ result.getEntity().setProperty("test6", EdmType.INT32, 6);
+ service.mergeEntity(TEST_TABLE_2, result.getEntity());
+
+ // Assert
+ }
+
+ @Test
+ public void deleteEntityWorks() throws Exception {
+ System.out.println("deleteEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("deleteEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity);
+
+ service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey());
+
+ // Assert
+ }
+
+ @Test
+ public void deleteEntityWithETagWorks() throws Exception {
+ System.out.println("deleteEntityWithETagWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("deleteEntityWithETagWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult result = service.insertEntity(TEST_TABLE_2, entity);
+
+ service.deleteEntity(TEST_TABLE_2, result.getEntity().getPartitionKey(), result.getEntity().getRowKey(),
+ new DeleteEntityOptions().setEtag(result.getEntity().getEtag()));
+
+ // Assert
+ }
+
+ @Test
+ public void getEntityWorks() throws Exception {
+ System.out.println("getEntityWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("getEntityWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ InsertEntityResult insertResult = service.insertEntity(TEST_TABLE_2, entity);
+ GetEntityResult result = service.getEntity(TEST_TABLE_2, insertResult.getEntity().getPartitionKey(),
+ insertResult.getEntity().getRowKey());
+
+ // Assert
+ assertNotNull(result);
+ assertNotNull(result.getEntity());
+
+ assertEquals("001", result.getEntity().getPartitionKey());
+ assertEquals("getEntityWorks", result.getEntity().getRowKey());
+ assertNotNull(result.getEntity().getTimestamp());
+ assertNotNull(result.getEntity().getEtag());
+
+ assertNotNull(result.getEntity().getProperty("test"));
+ assertEquals(true, result.getEntity().getProperty("test").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test2"));
+ assertEquals("value", result.getEntity().getProperty("test2").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test3"));
+ assertEquals(3, result.getEntity().getProperty("test3").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test4"));
+ assertEquals(12345678901L, result.getEntity().getProperty("test4").getValue());
+
+ assertNotNull(result.getEntity().getProperty("test5"));
+ assertTrue(result.getEntity().getProperty("test5").getValue() instanceof Date);
+ }
+
+ @Test
+ public void queryEntitiesWorks() throws Exception {
+ System.out.println("queryEntitiesWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ // Act
+ service.insertEntity(TEST_TABLE_3, entity);
+ QueryEntitiesResult result = service.queryEntities(TEST_TABLE_3);
+
+ // Assert
+ assertNotNull(result);
+ assertNotNull(result.getEntities());
+ assertEquals(1, result.getEntities().size());
+
+ assertNotNull(result.getEntities().get(0));
+
+ assertEquals("001", result.getEntities().get(0).getPartitionKey());
+ assertEquals("queryEntitiesWorks", result.getEntities().get(0).getRowKey());
+ assertNotNull(result.getEntities().get(0).getTimestamp());
+ assertNotNull(result.getEntities().get(0).getEtag());
+
+ assertNotNull(result.getEntities().get(0).getProperty("test"));
+ assertEquals(true, result.getEntities().get(0).getProperty("test").getValue());
+
+ assertNotNull(result.getEntities().get(0).getProperty("test2"));
+ assertEquals("value", result.getEntities().get(0).getProperty("test2").getValue());
+
+ assertNotNull(result.getEntities().get(0).getProperty("test3"));
+ assertEquals(3, result.getEntities().get(0).getProperty("test3").getValue());
+
+ assertNotNull(result.getEntities().get(0).getProperty("test4"));
+ assertEquals(12345678901L, result.getEntities().get(0).getProperty("test4").getValue());
+
+ assertNotNull(result.getEntities().get(0).getProperty("test5"));
+ assertTrue(result.getEntities().get(0).getProperty("test5").getValue() instanceof Date);
+ }
+
+ @Test
+ public void queryEntitiesWithPaginationWorks() throws Exception {
+ System.out.println("queryEntitiesWithPaginationWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_4;
+ int numberOfEntries = 20;
+ for (int i = 0; i < numberOfEntries; i++) {
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithPaginationWorks-" + i)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ service.insertEntity(table, entity);
+ }
+
+ // Act
+ int entryCount = 0;
+ String nextPartitionKey = null;
+ String nextRowKey = null;
+ while (true) {
+ QueryEntitiesResult result = service.queryEntities(table,
+ new QueryEntitiesOptions().setNextPartitionKey(nextPartitionKey).setNextRowKey(nextRowKey));
+
+ entryCount += result.getEntities().size();
+
+ if (nextPartitionKey == null)
+ break;
+
+ nextPartitionKey = result.getNextPartitionKey();
+ nextRowKey = result.getNextRowKey();
+ }
+
+ // Assert
+ assertEquals(numberOfEntries, entryCount);
+ }
+
+ @Test
+ public void queryEntitiesWithFilterWorks() throws Exception {
+ System.out.println("queryEntitiesWithFilterWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_5;
+ int numberOfEntries = 5;
+ for (int i = 0; i < numberOfEntries; i++) {
+ Entity entity = new Entity().setPartitionKey("001").setRowKey("queryEntitiesWithFilterWorks-" + i)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ service.insertEntity(table, entity);
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(table,
+ new QueryEntitiesOptions().setQuery(new Query().setFilter(Filter.eq(Filter.litteral("RowKey"),
+ Filter.constant("queryEntitiesWithFilterWorks-3")))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntities().size());
+ assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey());
+ }
+
+ {
+ // Act
+ QueryEntitiesResult result = service.queryEntities(table, new QueryEntitiesOptions().setQuery(new Query()
+ .setFilter(Filter.rawString("RowKey eq 'queryEntitiesWithFilterWorks-3'"))));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntities().size());
+ assertEquals("queryEntitiesWithFilterWorks-3", result.getEntities().get(0).getRowKey());
+ }
+ }
+
+ @Test
+ public void batchInsertWorks() throws Exception {
+ System.out.println("batchInsertWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+
+ // Act
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ BatchResult result = service.batch(new BatchOperations().addInsertEntity(table, entity));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(InsertEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchUpdateWorks() throws Exception {
+ System.out.println("batchUpdateWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchUpdateWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+ entity = service.insertEntity(table, entity).getEntity();
+
+ // Act
+ entity.setProperty("test", EdmType.BOOLEAN, false);
+ BatchResult result = service.batch(new BatchOperations().addUpdateEntity(table, entity));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchMergeWorks() throws Exception {
+ System.out.println("batchMergeWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchMergeWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+ entity = service.insertEntity(table, entity).getEntity();
+
+ // Act
+ BatchResult result = service.batch(new BatchOperations().addMergeEntity(table, entity));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchInsertOrReplaceWorks() throws Exception {
+ System.out.println("batchInsertOrReplaceWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+
+ // Act
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertOrReplaceWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ BatchResult result = service.batch(new BatchOperations().addInsertOrReplaceEntity(table, entity));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchInsertOrMergeWorks() throws Exception {
+ System.out.println("batchInsertOrMergeWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+
+ // Act
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchInsertOrMergeWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ BatchResult result = service.batch(new BatchOperations().addInsertOrMergeEntity(table, entity));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(UpdateEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchDeleteWorks() throws Exception {
+ System.out.println("batchDeleteWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_6;
+ String partitionKey = "001";
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchDeleteWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+ entity = service.insertEntity(table, entity).getEntity();
+
+ // Act
+ BatchResult result = service.batch(new BatchOperations().addDeleteEntity(table, entity.getPartitionKey(),
+ entity.getRowKey(), entity.getEtag()));
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(1, result.getEntries().size());
+ assertEquals(DeleteEntity.class, result.getEntries().get(0).getClass());
+ }
+
+ @Test
+ public void batchLotsOfInsertsWorks() throws Exception {
+ System.out.println("batchMultipleWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_7;
+ String partitionKey = "001";
+ int insertCount = 100;
+
+ // Act
+ BatchOperations batchOperations = new BatchOperations();
+ for (int i = 0; i < insertCount; i++) {
+
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchWorks-" + i)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ batchOperations.addInsertEntity(table, entity);
+ }
+ BatchResult result = service.batch(batchOperations);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(insertCount, result.getEntries().size());
+ for (int i = 0; i < insertCount; i++) {
+ assertEquals(InsertEntity.class, result.getEntries().get(i).getClass());
+
+ Entity entity = ((InsertEntity) result.getEntries().get(i)).getEntity();
+
+ assertEquals("001", entity.getPartitionKey());
+ assertEquals("batchWorks-" + i, entity.getRowKey());
+ assertNotNull(entity.getTimestamp());
+ assertNotNull(entity.getEtag());
+
+ assertNotNull(entity.getProperty("test"));
+ assertEquals(true, entity.getProperty("test").getValue());
+
+ assertNotNull(entity.getProperty("test2"));
+ assertEquals("value", entity.getProperty("test2").getValue());
+
+ assertNotNull(entity.getProperty("test3"));
+ assertEquals(3, entity.getProperty("test3").getValue());
+
+ assertNotNull(entity.getProperty("test4"));
+ assertEquals(12345678901L, entity.getProperty("test4").getValue());
+
+ assertNotNull(entity.getProperty("test5"));
+ assertTrue(entity.getProperty("test5").getValue() instanceof Date);
+ }
+ }
+
+ @Test
+ public void batchAllOperationsTogetherWorks() throws Exception {
+ System.out.println("batchAllOperationsWorks()");
+
+ // Arrange
+ Configuration config = createConfiguration();
+ TableContract service = TableService.create(config);
+ String table = TEST_TABLE_8;
+ String partitionKey = "001";
+
+ // Insert a few entities to allow updating them in batch
+ Entity entity1 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 1)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ entity1 = service.insertEntity(table, entity1).getEntity();
+
+ Entity entity2 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 2)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ entity2 = service.insertEntity(table, entity2).getEntity();
+
+ Entity entity3 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 3)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ entity3 = service.insertEntity(table, entity3).getEntity();
+
+ Entity entity4 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 4)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+
+ entity4 = service.insertEntity(table, entity4).getEntity();
+
+ // Act
+ BatchOperations batchOperations = new BatchOperations();
+
+ Entity entity = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks")
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+ batchOperations.addInsertEntity(table, entity);
+
+ batchOperations.addDeleteEntity(table, entity1.getPartitionKey(), entity1.getRowKey(), entity1.getEtag());
+
+ batchOperations.addUpdateEntity(table, entity2.setProperty("test", EdmType.INT32, 5));
+ batchOperations.addMergeEntity(table, entity3.setProperty("test", EdmType.INT32, 5));
+ batchOperations.addInsertOrReplaceEntity(table, entity4.setProperty("test", EdmType.INT32, 5));
+
+ Entity entity5 = new Entity().setPartitionKey(partitionKey).setRowKey("batchAllOperationsWorks-" + 5)
+ .setProperty("test", EdmType.BOOLEAN, true).setProperty("test2", EdmType.STRING, "value")
+ .setProperty("test3", EdmType.INT32, 3).setProperty("test4", EdmType.INT64, 12345678901L)
+ .setProperty("test5", EdmType.DATETIME, new Date());
+ batchOperations.addInsertOrMergeEntity(table, entity5);
+
+ BatchResult result = service.batch(batchOperations);
+
+ // Assert
+ assertNotNull(result);
+ assertEquals(batchOperations.getOperations().size(), result.getEntries().size());
+ assertEquals(InsertEntity.class, result.getEntries().get(0).getClass());
+ assertEquals(DeleteEntity.class, result.getEntries().get(1).getClass());
+ assertEquals(UpdateEntity.class, result.getEntries().get(2).getClass());
+ assertEquals(UpdateEntity.class, result.getEntries().get(3).getClass());
+ assertEquals(UpdateEntity.class, result.getEntries().get(4).getClass());
+ assertEquals(UpdateEntity.class, result.getEntries().get(5).getClass());
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java
new file mode 100644
index 000000000000..aa14a71f8597
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/AtomReaderWriterTests.java
@@ -0,0 +1,80 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.microsoft.windowsazure.services.blob.implementation.ISO8601DateConverter;
+import com.microsoft.windowsazure.services.core.utils.DefaultDateFactory;
+import com.microsoft.windowsazure.services.table.IntegrationTestBase;
+import com.microsoft.windowsazure.services.table.models.TableEntry;
+
+public class AtomReaderWriterTests extends IntegrationTestBase {
+ @Test
+ public void parseTableEntriesWorks() throws Exception {
+ // Arrange
+ AtomReaderWriter atom = new AtomReaderWriter(new DefaultXMLStreamFactory(), new DefaultDateFactory(),
+ new ISO8601DateConverter(), new DefaultEdmValueConterter(new ISO8601DateConverter()));
+ String feed = "\r\n"
+ + "\r\n"
+ + " Tables\r\n"
+ + " http://rpaquaytest.table.core.windows.net/Tables\r\n"
+ + " 2012-01-10T21:23:30Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " http://rpaquaytest.table.core.windows.net/Tables('sdktest1')\r\n"
+ + " \r\n"
+ + " 2012-01-10T21:23:30Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " sdktest1\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " http://rpaquaytest.table.core.windows.net/Tables('sdktest10')\r\n"
+ + " \r\n"
+ + " 2012-01-10T21:23:30Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n" + " \r\n"
+ + " sdktest10\r\n" + " \r\n"
+ + " \r\n" + " \r\n" + "\r\n";
+ InputStream stream = new ByteArrayInputStream(feed.getBytes("UTF-8"));
+
+ // Act
+ List entries = atom.parseTableEntries(stream);
+
+ // Assert
+ assertNotNull(entries);
+ assertEquals(2, entries.size());
+ assertEquals("sdktest1", entries.get(0).getName());
+ assertEquals("sdktest10", entries.get(1).getName());
+ }
+}
diff --git a/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java
new file mode 100644
index 000000000000..c01fdeaebc3c
--- /dev/null
+++ b/microsoft-azure-api/src/test/java/com/microsoft/windowsazure/services/table/implementation/MimeMultipartTests.java
@@ -0,0 +1,336 @@
+/**
+ * Copyright 2011 Microsoft Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.microsoft.windowsazure.services.table.implementation;
+
+import static org.junit.Assert.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.MultipartDataSource;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.junit.Test;
+
+import com.microsoft.windowsazure.services.table.IntegrationTestBase;
+
+public class MimeMultipartTests extends IntegrationTestBase {
+ @Test
+ public void parseMimeWorks() throws Exception {
+ //@formatter:off
+ String s = "--batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb \r\n"
+ + "Content-Type: multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 201 Created\r\n"
+ + "Content-ID: 1\r\n"
+ + "Content-Type: application/atom+xml;charset=utf-8\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n"
+ + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='1')\r\n"
+ + " \r\n"
+ + " 2009-04-30T20:44:09Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Channel_19\r\n"
+ + " 1\r\n"
+ + " 2009-04-30T20:44:09.5789464Z\r\n"
+ + " .Net...\r\n"
+ + " 9\r\n"
+ + " \r\n"
+ + " \r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 201 Created\r\n"
+ + "Content-ID: 2\r\n"
+ + "Content-Type: application/atom+xml;charset=utf-8\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A09.5789464Z'\"\r\n"
+ + "Location: http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "\r\n"
+ + "\r\n"
+ + " http://myaccount.tables.core.windows.net/Blogs(PartitionKey='Channel_19',RowKey='2')\r\n"
+ + " \r\n"
+ + " 2009-04-30T20:44:09Z\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Channel_19\r\n"
+ + " 2\r\n"
+ + " 2009-04-30T20:44:09.5789464Z\r\n"
+ + " Azure...\r\n"
+ + " 9\r\n"
+ + " \r\n"
+ + " \r\n"
+ + "\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 204 No Content\r\n"
+ + "Content-ID: 3\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "ETag: W/\"datetime'2009-04-30T20%3A44%3A10.0019041Z'\"\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977\r\n"
+ + "Content-Type: application/http\r\n"
+ + "Content-Transfer-Encoding: binary\r\n"
+ + "\r\n"
+ + "HTTP/1.1 204 No Content\r\n"
+ + "Content-ID: 4\r\n"
+ + "Cache-Control: no-cache\r\n"
+ + "DataServiceVersion: 1.0;\r\n"
+ + "\r\n"
+ + "--changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977--\r\n"
+ + "--batchresponse_4c637ba4-b2f8-40f8-8856-c2d10d163a83--\r\n";
+ //@formatter:on
+
+ DataSource ds = new ByteArrayDataSource(s,
+ "multipart/mixed; boundary=batchresponse_dc0fea8c-ed83-4aa8-ac9b-bf56a2d46dfb");
+ MimeMultipart m = new MimeMultipart(ds);
+
+ assertEquals(1, m.getCount());
+ assertTrue(m.getBodyPart(0) instanceof MimeBodyPart);
+
+ MimeBodyPart part = (MimeBodyPart) m.getBodyPart(0);
+ String contentType = part.getHeader("Content-Type", ":");
+ assertEquals("multipart/mixed; boundary=changesetresponse_8a28b620-b4bb-458c-a177-0959fb14c977", contentType);
+
+ DataSource ds2 = new ByteArrayDataSource(part.getInputStream(), contentType);
+ MimeMultipart m2 = new MimeMultipart(ds2);
+
+ assertEquals(4, m2.getCount());
+ }
+
+ @Test
+ public void buildMimeWorks() throws Exception {
+ //@formatter:off
+ String changeset1 = "POST http://myaccount.tables.core.windows.net/Blogs HTTP/1.1\r\n" +
+ "Content-ID: 1\r\n" +
+ "Content-Type: application/atom+xml;type=entry\r\n" +
+ "Content-Length: ###\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " \r\n" +
+ " 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 e7bf06b034de..7739acd3e733 100644
--- a/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties
+++ b/microsoft-azure-api/src/test/resources/META-INF/com.microsoft.windowsazure.properties
@@ -7,4 +7,7 @@ blob.accountKey=%BLOB_ACCOUNTKEY%
blob.uri=http://%BLOB_ACCOUNTNAME%.blob.core.windows.net
queue.accountName=%QUEUE_ACCOUNTNAME%
queue.accountKey=%QUEUE_ACCOUNTKEY%
-queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net
\ No newline at end of file
+queue.uri=http://%QUEUE_ACCOUNTNAME%.queue.core.windows.net
+table.accountName=%TABLE_ACCOUNTNAME%
+table.accountKey=%TABLE_ACCOUNTKEY%
+table.uri=http://%TABLE_ACCOUNTNAME%.table.core.windows.net
\ No newline at end of file