> bindings() {
+ return bindings;
+ }
+
+ /**
+ * The policy's etag.
+ *
+ * Etags are used for optimistic concurrency control as a way to help prevent simultaneous
+ * updates of a policy from overwriting each other. It is strongly suggested that systems make
+ * use of the etag in the read-modify-write cycle to perform policy updates in order to avoid
+ * race conditions. An etag is returned in the response to getIamPolicy, and systems are
+ * expected to put that etag in the request to setIamPolicy to ensure that their change will be
+ * applied to the same version of the policy. If no etag is provided in the call to
+ * setIamPolicy, then the existing policy is overwritten blindly.
+ */
+ public String etag() {
+ return etag;
+ }
+
+ /**
+ * Sets the version of the policy. The default version is 0, meaning only the "owner", "editor",
+ * and "viewer" roles are permitted. If the version is 1, you may also use other roles.
+ */
+ public Integer version() {
+ return version;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(getClass(), bindings, etag, version);
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (obj == null || !getClass().equals(obj.getClass())) {
+ return false;
+ }
+ @SuppressWarnings("rawtypes")
+ IamPolicy other = (IamPolicy) obj;
+ return Objects.equals(bindings, other.bindings())
+ && Objects.equals(etag, other.etag())
+ && Objects.equals(version, other.version());
+ }
+}
diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/Identity.java b/gcloud-java-core/src/main/java/com/google/gcloud/Identity.java
new file mode 100644
index 000000000000..687a76ffc42c
--- /dev/null
+++ b/gcloud-java-core/src/main/java/com/google/gcloud/Identity.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.CaseFormat;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * An identity in an {@link IamPolicy}. The following types of identities are permitted in IAM
+ * policies:
+ *
+ * - Google account
+ *
- Service account
+ *
- Google group
+ *
- Google Apps domain
+ *
+ *
+ * There are also two special identities that represent all users and all Google-authenticated
+ * accounts.
+ *
+ * @see Concepts
+ * related to identity
+ */
+public final class Identity implements Serializable {
+
+ private static final long serialVersionUID = -8181841964597657446L;
+
+ private final Type type;
+ private final String value;
+
+ /**
+ * The types of IAM identities.
+ */
+ public enum Type {
+
+ /**
+ * Represents anyone who is on the internet; with or without a Google account.
+ */
+ ALL_USERS,
+
+ /**
+ * Represents anyone who is authenticated with a Google account or a service account.
+ */
+ ALL_AUTHENTICATED_USERS,
+
+ /**
+ * Represents a specific Google account.
+ */
+ USER,
+
+ /**
+ * Represents a service account.
+ */
+ SERVICE_ACCOUNT,
+
+ /**
+ * Represents a Google group.
+ */
+ GROUP,
+
+ /**
+ * Represents all the users of a Google Apps domain name.
+ */
+ DOMAIN
+ }
+
+ private Identity(Type type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ /**
+ * Returns the string identifier for this identity. The value corresponds to:
+ *
+ * - email address (for identities of type {@code USER}, {@code SERVICE_ACCOUNT}, and
+ * {@code GROUP})
+ *
- domain (for identities of type {@code DOMAIN})
+ *
- {@code null} (for identities of type {@code ALL_USERS} and
+ * {@code ALL_AUTHENTICATED_USERS})
+ *
+ */
+ public String value() {
+ return value;
+ }
+
+ /**
+ * Returns a new identity representing anyone who is on the internet; with or without a Google
+ * account.
+ */
+ public static Identity allUsers() {
+ return new Identity(Type.ALL_USERS, null);
+ }
+
+ /**
+ * Returns a new identity representing anyone who is authenticated with a Google account or a
+ * service account.
+ */
+ public static Identity allAuthenticatedUsers() {
+ return new Identity(Type.ALL_AUTHENTICATED_USERS, null);
+ }
+
+ /**
+ * Returns a new user identity.
+ *
+ * @param email An email address that represents a specific Google account. For example,
+ * alice@gmail.com or joe@example.com.
+ */
+ public static Identity user(String email) {
+ return new Identity(Type.USER, checkNotNull(email));
+ }
+
+ /**
+ * Returns a new service account identity.
+ *
+ * @param email An email address that represents a service account. For example,
+ * my-other-app@appspot.gserviceaccount.com.
+ */
+ public static Identity serviceAccount(String email) {
+ return new Identity(Type.SERVICE_ACCOUNT, checkNotNull(email));
+ }
+
+ /**
+ * Returns a new group identity.
+ *
+ * @param email An email address that represents a Google group. For example,
+ * admins@example.com.
+ */
+ public static Identity group(String email) {
+ return new Identity(Type.GROUP, checkNotNull(email));
+ }
+
+ /**
+ * Returns a new domain identity.
+ *
+ * @param domain A Google Apps domain name that represents all the users of that domain. For
+ * example, google.com or example.com.
+ */
+ public static Identity domain(String domain) {
+ return new Identity(Type.DOMAIN, checkNotNull(domain));
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(value, type);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Identity)) {
+ return false;
+ }
+ Identity other = (Identity) obj;
+ return Objects.equals(value, other.value()) && Objects.equals(type, other.type());
+ }
+
+ /**
+ * Returns the string value associated with the identity. Used primarily for converting from
+ * {@code Identity} objects to strings for protobuf-generated policies.
+ */
+ public String strValue() {
+ switch (type) {
+ case ALL_USERS:
+ return "allUsers";
+ case ALL_AUTHENTICATED_USERS:
+ return "allAuthenticatedUsers";
+ case USER:
+ return "user:" + value;
+ case SERVICE_ACCOUNT:
+ return "serviceAccount:" + value;
+ case GROUP:
+ return "group:" + value;
+ case DOMAIN:
+ return "domain:" + value;
+ default:
+ throw new IllegalStateException("Unexpected identity type: " + type);
+ }
+ }
+
+ /**
+ * Converts a string to an {@code Identity}. Used primarily for converting protobuf-generated
+ * policy identities to {@code Identity} objects.
+ */
+ public static Identity valueOf(String identityStr) {
+ String[] info = identityStr.split(":");
+ Type type = Type.valueOf(CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, info[0]));
+ switch (type) {
+ case ALL_USERS:
+ return Identity.allUsers();
+ case ALL_AUTHENTICATED_USERS:
+ return Identity.allAuthenticatedUsers();
+ case USER:
+ return Identity.user(info[1]);
+ case SERVICE_ACCOUNT:
+ return Identity.serviceAccount(info[1]);
+ case GROUP:
+ return Identity.group(info[1]);
+ case DOMAIN:
+ return Identity.domain(info[1]);
+ default:
+ throw new IllegalStateException("Unexpected identity type " + type);
+ }
+ }
+}
diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/Restorable.java b/gcloud-java-core/src/main/java/com/google/gcloud/Restorable.java
index 90633c70046f..0b573522e370 100644
--- a/gcloud-java-core/src/main/java/com/google/gcloud/Restorable.java
+++ b/gcloud-java-core/src/main/java/com/google/gcloud/Restorable.java
@@ -21,14 +21,14 @@
*
*
* A typical capture usage:
- *
{@code
+ * {@code
* X restorableObj; // X instanceof Restorable
* RestorableState state = restorableObj.capture();
* .. persist state
* }
*
* A typical restore usage:
- * {@code
+ * {@code
* RestorableState state = ... // read from persistence
* X restorableObj = state.restore();
* ...
diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java b/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java
index 31e543809464..d45069434a26 100644
--- a/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java
+++ b/gcloud-java-core/src/main/java/com/google/gcloud/ServiceOptions.java
@@ -523,9 +523,10 @@ public RetryParams retryParams() {
* options.
*/
public HttpRequestInitializer httpRequestInitializer() {
- final HttpRequestInitializer delegate = authCredentials() != null
- ? new HttpCredentialsAdapter(authCredentials().credentials().createScoped(scopes()))
- : null;
+ final HttpRequestInitializer delegate =
+ authCredentials() != null && authCredentials.credentials() != null
+ ? new HttpCredentialsAdapter(authCredentials().credentials().createScoped(scopes()))
+ : null;
return new HttpRequestInitializer() {
@Override
public void initialize(HttpRequest httpRequest) throws IOException {
diff --git a/gcloud-java-core/src/main/java/com/google/gcloud/package-info.java b/gcloud-java-core/src/main/java/com/google/gcloud/package-info.java
new file mode 100644
index 000000000000..d527640c99f9
--- /dev/null
+++ b/gcloud-java-core/src/main/java/com/google/gcloud/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Core classes for the {@code gcloud-java} library.
+ */
+package com.google.gcloud;
diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/BaseSerializationTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/BaseSerializationTest.java
new file mode 100644
index 000000000000..e9ab3d47984b
--- /dev/null
+++ b/gcloud-java-core/src/test/java/com/google/gcloud/BaseSerializationTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * Base class for serialization tests. To use this class in your tests override the
+ * {@code serializableObjects()} method to return all objects that must be serializable. Also
+ * override {@code restorableObjects()} method to return all restorable objects whose state must be
+ * tested for proper serialization. Both methods can return {@code null} if no such object needs to
+ * be tested.
+ */
+public abstract class BaseSerializationTest {
+
+ /**
+ * Returns all objects for which correct serialization must be tested.
+ */
+ protected abstract Serializable[] serializableObjects();
+
+ /**
+ * Returns all restorable objects whose state must be tested for proper serialization.
+ */
+ protected abstract Restorable>[] restorableObjects();
+
+ @Test
+ public void testSerializableObjects() throws Exception {
+ for (Serializable obj : firstNonNull(serializableObjects(), new Serializable[0])) {
+ Object copy = serializeAndDeserialize(obj);
+ assertEquals(obj, obj);
+ assertEquals(obj, copy);
+ assertEquals(obj.hashCode(), copy.hashCode());
+ assertEquals(obj.toString(), copy.toString());
+ assertNotSame(obj, copy);
+ assertEquals(copy, copy);
+ }
+ }
+
+ @Test
+ public void testRestorableObjects() throws Exception {
+ for (Restorable restorable : firstNonNull(restorableObjects(), new Restorable[0])) {
+ RestorableState> state = restorable.capture();
+ RestorableState> deserializedState = serializeAndDeserialize(state);
+ assertEquals(state, deserializedState);
+ assertEquals(state.hashCode(), deserializedState.hashCode());
+ assertEquals(state.toString(), deserializedState.toString());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public T serializeAndDeserialize(T obj) throws IOException, ClassNotFoundException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
+ output.writeObject(obj);
+ }
+ try (ObjectInputStream input =
+ new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
+ return (T) input.readObject();
+ }
+ }
+}
diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/BaseWriteChannelTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/BaseWriteChannelTest.java
index e49a17b019e0..6d5306a3bc7f 100644
--- a/gcloud-java-core/src/test/java/com/google/gcloud/BaseWriteChannelTest.java
+++ b/gcloud-java-core/src/test/java/com/google/gcloud/BaseWriteChannelTest.java
@@ -32,6 +32,7 @@
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.Random;
@@ -102,8 +103,7 @@ public void testClose() throws IOException {
@Test
public void testValidateOpen() throws IOException {
channel.close();
- thrown.expect(IOException.class);
- thrown.expectMessage("stream is closed");
+ thrown.expect(ClosedChannelException.class);
channel.write(ByteBuffer.allocate(42));
}
diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/IamPolicyTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/IamPolicyTest.java
new file mode 100644
index 000000000000..235c2c2b1c85
--- /dev/null
+++ b/gcloud-java-core/src/test/java/com/google/gcloud/IamPolicyTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class IamPolicyTest {
+
+ private static final Identity ALL_USERS = Identity.allUsers();
+ private static final Identity ALL_AUTH_USERS = Identity.allAuthenticatedUsers();
+ private static final Identity USER = Identity.user("abc@gmail.com");
+ private static final Identity SERVICE_ACCOUNT =
+ Identity.serviceAccount("service-account@gmail.com");
+ private static final Identity GROUP = Identity.group("group@gmail.com");
+ private static final Identity DOMAIN = Identity.domain("google.com");
+ private static final Map> BINDINGS = ImmutableMap.of(
+ "viewer",
+ ImmutableSet.of(USER, SERVICE_ACCOUNT, ALL_USERS),
+ "editor",
+ ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN));
+ private static final PolicyImpl SIMPLE_POLICY = PolicyImpl.builder()
+ .addIdentity("viewer", USER, SERVICE_ACCOUNT, ALL_USERS)
+ .addIdentity("editor", ALL_AUTH_USERS, GROUP, DOMAIN)
+ .build();
+ private static final PolicyImpl FULL_POLICY =
+ new PolicyImpl.Builder(SIMPLE_POLICY.bindings(), "etag", 1).build();
+
+ static class PolicyImpl extends IamPolicy {
+
+ static class Builder extends IamPolicy.Builder {
+
+ private Builder() {}
+
+ private Builder(Map> bindings, String etag, Integer version) {
+ bindings(bindings).etag(etag).version(version);
+ }
+
+ @Override
+ public PolicyImpl build() {
+ return new PolicyImpl(this);
+ }
+ }
+
+ PolicyImpl(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder(bindings(), etag(), version());
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+ }
+
+ @Test
+ public void testBuilder() {
+ assertEquals(BINDINGS, FULL_POLICY.bindings());
+ assertEquals("etag", FULL_POLICY.etag());
+ assertEquals(1, FULL_POLICY.version().intValue());
+ Map> editorBinding =
+ ImmutableMap.>builder().put("editor", BINDINGS.get("editor")).build();
+ PolicyImpl policy = FULL_POLICY.toBuilder().bindings(editorBinding).build();
+ assertEquals(editorBinding, policy.bindings());
+ assertEquals("etag", policy.etag());
+ assertEquals(1, policy.version().intValue());
+ policy = SIMPLE_POLICY.toBuilder().removeRole("editor").build();
+ assertEquals(ImmutableMap.of("viewer", BINDINGS.get("viewer")), policy.bindings());
+ assertNull(policy.etag());
+ assertNull(policy.version());
+ policy = policy.toBuilder()
+ .removeIdentity("viewer", USER, ALL_USERS)
+ .addIdentity("viewer", DOMAIN, GROUP)
+ .build();
+ assertEquals(ImmutableMap.of("viewer", ImmutableSet.of(SERVICE_ACCOUNT, DOMAIN, GROUP)),
+ policy.bindings());
+ assertNull(policy.etag());
+ assertNull(policy.version());
+ policy = PolicyImpl.builder()
+ .removeIdentity("viewer", USER)
+ .addIdentity("owner", USER, SERVICE_ACCOUNT)
+ .addIdentity("editor", GROUP)
+ .removeIdentity("editor", GROUP)
+ .build();
+ assertEquals(
+ ImmutableMap.of("owner", ImmutableSet.of(USER, SERVICE_ACCOUNT)), policy.bindings());
+ assertNull(policy.etag());
+ assertNull(policy.version());
+ }
+
+ @Test
+ public void testIllegalPolicies() {
+ try {
+ PolicyImpl.builder().addIdentity(null, USER);
+ fail("Null role should cause exception.");
+ } catch (NullPointerException ex) {
+ assertEquals("The role cannot be null.", ex.getMessage());
+ }
+ try {
+ PolicyImpl.builder().addIdentity("viewer", null, USER);
+ fail("Null identity should cause exception.");
+ } catch (NullPointerException ex) {
+ assertEquals("Null identities are not permitted.", ex.getMessage());
+ }
+ try {
+ PolicyImpl.builder().addIdentity("viewer", USER, (Identity[]) null);
+ fail("Null identity should cause exception.");
+ } catch (NullPointerException ex) {
+ assertEquals("Null identities are not permitted.", ex.getMessage());
+ }
+ try {
+ PolicyImpl.builder().bindings(null);
+ fail("Null bindings map should cause exception.");
+ } catch (NullPointerException ex) {
+ assertEquals("The provided map of bindings cannot be null.", ex.getMessage());
+ }
+ try {
+ Map> bindings = new HashMap<>();
+ bindings.put("viewer", null);
+ PolicyImpl.builder().bindings(bindings);
+ fail("Null set of identities should cause exception.");
+ } catch (NullPointerException ex) {
+ assertEquals("A role cannot be assigned to a null set of identities.", ex.getMessage());
+ }
+ try {
+ Map> bindings = new HashMap<>();
+ Set identities = new HashSet<>();
+ identities.add(null);
+ bindings.put("viewer", identities);
+ PolicyImpl.builder().bindings(bindings);
+ fail("Null identity should cause exception.");
+ } catch (IllegalArgumentException ex) {
+ assertEquals("Null identities are not permitted.", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ assertNotNull(FULL_POLICY);
+ PolicyImpl emptyPolicy = PolicyImpl.builder().build();
+ AnotherPolicyImpl anotherPolicy = new AnotherPolicyImpl.Builder().build();
+ assertNotEquals(emptyPolicy, anotherPolicy);
+ assertNotEquals(emptyPolicy.hashCode(), anotherPolicy.hashCode());
+ assertNotEquals(FULL_POLICY, SIMPLE_POLICY);
+ assertNotEquals(FULL_POLICY.hashCode(), SIMPLE_POLICY.hashCode());
+ PolicyImpl copy = SIMPLE_POLICY.toBuilder().build();
+ assertEquals(SIMPLE_POLICY, copy);
+ assertEquals(SIMPLE_POLICY.hashCode(), copy.hashCode());
+ }
+
+ @Test
+ public void testBindings() {
+ assertTrue(PolicyImpl.builder().build().bindings().isEmpty());
+ assertEquals(BINDINGS, SIMPLE_POLICY.bindings());
+ }
+
+ @Test
+ public void testEtag() {
+ assertNull(SIMPLE_POLICY.etag());
+ assertEquals("etag", FULL_POLICY.etag());
+ }
+
+ @Test
+ public void testVersion() {
+ assertNull(SIMPLE_POLICY.version());
+ assertEquals(1, FULL_POLICY.version().intValue());
+ }
+
+ static class AnotherPolicyImpl extends IamPolicy {
+
+ static class Builder extends IamPolicy.Builder {
+
+ private Builder() {}
+
+ @Override
+ public AnotherPolicyImpl build() {
+ return new AnotherPolicyImpl(this);
+ }
+ }
+
+ AnotherPolicyImpl(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder();
+ }
+ }
+}
diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/IdentityTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/IdentityTest.java
new file mode 100644
index 000000000000..a42bc9db7abd
--- /dev/null
+++ b/gcloud-java-core/src/test/java/com/google/gcloud/IdentityTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+ private static final Identity ALL_USERS = Identity.allUsers();
+ private static final Identity ALL_AUTH_USERS = Identity.allAuthenticatedUsers();
+ private static final Identity USER = Identity.user("abc@gmail.com");
+ private static final Identity SERVICE_ACCOUNT =
+ Identity.serviceAccount("service-account@gmail.com");
+ private static final Identity GROUP = Identity.group("group@gmail.com");
+ private static final Identity DOMAIN = Identity.domain("google.com");
+
+ @Test
+ public void testAllUsers() {
+ assertEquals(Identity.Type.ALL_USERS, ALL_USERS.type());
+ assertNull(ALL_USERS.value());
+ }
+
+ @Test
+ public void testAllAuthenticatedUsers() {
+ assertEquals(Identity.Type.ALL_AUTHENTICATED_USERS, ALL_AUTH_USERS.type());
+ assertNull(ALL_AUTH_USERS.value());
+ }
+
+ @Test
+ public void testUser() {
+ assertEquals(Identity.Type.USER, USER.type());
+ assertEquals("abc@gmail.com", USER.value());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testUserNullEmail() {
+ Identity.user(null);
+ }
+
+ @Test
+ public void testServiceAccount() {
+ assertEquals(Identity.Type.SERVICE_ACCOUNT, SERVICE_ACCOUNT.type());
+ assertEquals("service-account@gmail.com", SERVICE_ACCOUNT.value());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testServiceAccountNullEmail() {
+ Identity.serviceAccount(null);
+ }
+
+ @Test
+ public void testGroup() {
+ assertEquals(Identity.Type.GROUP, GROUP.type());
+ assertEquals("group@gmail.com", GROUP.value());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testGroupNullEmail() {
+ Identity.group(null);
+ }
+
+ @Test
+ public void testDomain() {
+ assertEquals(Identity.Type.DOMAIN, DOMAIN.type());
+ assertEquals("google.com", DOMAIN.value());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testDomainNullId() {
+ Identity.domain(null);
+ }
+
+ @Test
+ public void testIdentityToAndFromPb() {
+ compareIdentities(ALL_USERS, Identity.valueOf(ALL_USERS.strValue()));
+ compareIdentities(ALL_AUTH_USERS, Identity.valueOf(ALL_AUTH_USERS.strValue()));
+ compareIdentities(USER, Identity.valueOf(USER.strValue()));
+ compareIdentities(SERVICE_ACCOUNT, Identity.valueOf(SERVICE_ACCOUNT.strValue()));
+ compareIdentities(GROUP, Identity.valueOf(GROUP.strValue()));
+ compareIdentities(DOMAIN, Identity.valueOf(DOMAIN.strValue()));
+ }
+
+ private void compareIdentities(Identity expected, Identity actual) {
+ assertEquals(expected, actual);
+ assertEquals(expected.type(), actual.type());
+ assertEquals(expected.value(), actual.value());
+ }
+}
diff --git a/gcloud-java-core/src/test/java/com/google/gcloud/SerializationTest.java b/gcloud-java-core/src/test/java/com/google/gcloud/SerializationTest.java
new file mode 100644
index 000000000000..3255a17333aa
--- /dev/null
+++ b/gcloud-java-core/src/test/java/com/google/gcloud/SerializationTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.Serializable;
+
+public class SerializationTest extends BaseSerializationTest {
+
+ private static class SomeIamPolicy extends IamPolicy {
+
+ private static final long serialVersionUID = 271243551016958285L;
+
+ private static class Builder extends IamPolicy.Builder {
+
+ @Override
+ public SomeIamPolicy build() {
+ return new SomeIamPolicy(this);
+ }
+ }
+
+ protected SomeIamPolicy(Builder builder) {
+ super(builder);
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder();
+ }
+ }
+
+ private static final BaseServiceException BASE_SERVICE_EXCEPTION =
+ new BaseServiceException(42, "message", "reason", true);
+ private static final ExceptionHandler EXCEPTION_HANDLER = ExceptionHandler.defaultInstance();
+ private static final Identity IDENTITY = Identity.allAuthenticatedUsers();
+ private static final PageImpl PAGE =
+ new PageImpl<>(null, "cursor", ImmutableList.of("string1", "string2"));
+ private static final RetryParams RETRY_PARAMS = RetryParams.defaultInstance();
+ private static final SomeIamPolicy SOME_IAM_POLICY = new SomeIamPolicy.Builder().build();
+ private static final String JSON_KEY = "{\n"
+ + " \"private_key_id\": \"somekeyid\",\n"
+ + " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggS"
+ + "kAgEAAoIBAQC+K2hSuFpAdrJI\\nnCgcDz2M7t7bjdlsadsasad+fvRSW6TjNQZ3p5LLQY1kSZRqBqylRkzteMOyHg"
+ + "aR\\n0Pmxh3ILCND5men43j3h4eDbrhQBuxfEMalkG92sL+PNQSETY2tnvXryOvmBRwa/\\nQP/9dJfIkIDJ9Fw9N4"
+ + "Bhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nknddadwkwewcVxHFhcZJO+XWf6ofLUXpRwiTZakGMn8EE1uVa2"
+ + "LgczOjwWHGi99MFjxSer5m9\\n1tCa3/KEGKiS/YL71JvjwX3mb+cewlkcmweBKZHM2JPTk0ZednFSpVZMtycjkbLa"
+ + "\\ndYOS8V85AgMBewECggEBAKksaldajfDZDV6nGqbFjMiizAKJolr/M3OQw16K6o3/\\n0S31xIe3sSlgW0+UbYlF"
+ + "4U8KifhManD1apVSC3csafaspP4RZUHFhtBywLO9pR5c\\nr6S5aLp+gPWFyIp1pfXbWGvc5VY/v9x7ya1VEa6rXvL"
+ + "sKupSeWAW4tMj3eo/64ge\\nsdaceaLYw52KeBYiT6+vpsnYrEkAHO1fF/LavbLLOFJmFTMxmsNaG0tuiJHgjshB\\"
+ + "n82DpMCbXG9YcCgI/DbzuIjsdj2JC1cascSP//3PmefWysucBQe7Jryb6NQtASmnv\\nCdDw/0jmZTEjpe4S1lxfHp"
+ + "lAhHFtdgYTvyYtaLZiVVkCgYEA8eVpof2rceecw/I6\\n5ng1q3Hl2usdWV/4mZMvR0fOemacLLfocX6IYxT1zA1FF"
+ + "JlbXSRsJMf/Qq39mOR2\\nSpW+hr4jCoHeRVYLgsbggtrevGmILAlNoqCMpGZ6vDmJpq6ECV9olliDvpPgWOP+\\nm"
+ + "YPDreFBGxWvQrADNbRt2dmGsrsCgYEAyUHqB2wvJHFqdmeBsaacewzV8x9WgmeX\\ngUIi9REwXlGDW0Mz50dxpxcK"
+ + "CAYn65+7TCnY5O/jmL0VRxU1J2mSWyWTo1C+17L0\\n3fUqjxL1pkefwecxwecvC+gFFYdJ4CQ/MHHXU81Lwl1iWdF"
+ + "Cd2UoGddYaOF+KNeM\\nHC7cmqra+JsCgYEAlUNywzq8nUg7282E+uICfCB0LfwejuymR93CtsFgb7cRd6ak\\nECR"
+ + "8FGfCpH8ruWJINllbQfcHVCX47ndLZwqv3oVFKh6pAS/vVI4dpOepP8++7y1u\\ncoOvtreXCX6XqfrWDtKIvv0vjl"
+ + "HBhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nkndj5uNl5SiuVxHFhcZJO+XWf6ofLUregtevZakGMn8EE1uVa"
+ + "2AY7eafmoU/nZPT\\n00YB0TBATdCbn/nBSuKDESkhSg9s2GEKQZG5hBmL5uCMfo09z3SfxZIhJdlerreP\\nJ7gSi"
+ + "dI12N+EZxYd4xIJh/HFDgp7RRO87f+WJkofMQKBgGTnClK1VMaCRbJZPriw\\nEfeFCoOX75MxKwXs6xgrw4W//AYG"
+ + "GUjDt83lD6AZP6tws7gJ2IwY/qP7+lyhjEqN\\nHtfPZRGFkGZsdaksdlaksd323423d+15/UvrlRSFPNj1tWQmNKk"
+ + "XyRDW4IG1Oa2p\\nrALStNBx5Y9t0/LQnFI4w3aG\\n-----END PRIVATE KEY-----\\n\",\n"
+ + " \"client_email\": \"someclientid@developer.gserviceaccount.com\",\n"
+ + " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ + " \"type\": \"service_account\"\n"
+ + "}";
+
+ @Override
+ protected Serializable[] serializableObjects() {
+ return new Serializable[]{BASE_SERVICE_EXCEPTION, EXCEPTION_HANDLER, IDENTITY, PAGE,
+ RETRY_PARAMS, SOME_IAM_POLICY};
+ }
+
+ @Override
+ protected Restorable>[] restorableObjects() {
+ try {
+ return new Restorable>[]{AuthCredentials.createForAppEngine(), AuthCredentials.noAuth(),
+ AuthCredentials.createForJson(new ByteArrayInputStream(JSON_KEY.getBytes()))};
+ } catch (IOException ex) {
+ // never reached
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/gcloud-java-datastore/README.md b/gcloud-java-datastore/README.md
index 8c0ae1950e77..0d89a0a07e3e 100644
--- a/gcloud-java-datastore/README.md
+++ b/gcloud-java-datastore/README.md
@@ -7,6 +7,7 @@ Java idiomatic client for [Google Cloud Datastore] (https://cloud.google.com/dat
[![Coverage Status](https://coveralls.io/repos/GoogleCloudPlatform/gcloud-java/badge.svg?branch=master)](https://coveralls.io/r/GoogleCloudPlatform/gcloud-java?branch=master)
[![Maven](https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-datastore.svg)]( https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-datastore.svg)
[![Codacy Badge](https://api.codacy.com/project/badge/grade/9da006ad7c3a4fe1abd142e77c003917)](https://www.codacy.com/app/mziccard/gcloud-java)
+[![Dependency Status](https://www.versioneye.com/user/projects/56bd8ee72a29ed002d2b0969/badge.svg?style=flat)](https://www.versioneye.com/user/projects/56bd8ee72a29ed002d2b0969)
- [Homepage] (https://googlecloudplatform.github.io/gcloud-java/)
- [API Documentation] (http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/datastore/package-summary.html)
@@ -21,21 +22,21 @@ If you are using Maven, add this to your pom.xml file
com.google.gcloud
gcloud-java-datastore
- 0.1.3
+ 0.1.5
```
If you are using Gradle, add this to your dependencies
```Groovy
-compile 'com.google.gcloud:gcloud-java-datastore:0.1.3'
+compile 'com.google.gcloud:gcloud-java-datastore:0.1.5'
```
If you are using SBT, add this to your dependencies
```Scala
-libraryDependencies += "com.google.gcloud" % "gcloud-java-datastore" % "0.1.3"
+libraryDependencies += "com.google.gcloud" % "gcloud-java-datastore" % "0.1.5"
```
Example Application
--------------------
-[`DatastoreExample`](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/datastore/DatastoreExample.java) is a simple command line interface for the Cloud Datastore. Read more about using the application on the [`gcloud-java-examples` docs page](http://googlecloudplatform.github.io/gcloud-java/apidocs/?com/google/gcloud/examples/DatastoreExample.html).
+[`DatastoreExample`](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/datastore/DatastoreExample.java) is a simple command line interface for the Cloud Datastore. Read more about using the application on the [`DatastoreExample` docs page](http://googlecloudplatform.github.io/gcloud-java/apidocs/?com/google/gcloud/examples/datastore/DatastoreExample.html).
Authentication
--------------
diff --git a/gcloud-java-datastore/pom.xml b/gcloud-java-datastore/pom.xml
index a76e4f9d9cbd..f3b46e22b3c8 100644
--- a/gcloud-java-datastore/pom.xml
+++ b/gcloud-java-datastore/pom.xml
@@ -10,7 +10,7 @@
com.google.gcloud
gcloud-java-pom
- 0.1.4-SNAPSHOT
+ 0.1.6-SNAPSHOT
gcloud-java-datastore
@@ -24,7 +24,7 @@
com.google.apis
google-api-services-datastore-protobuf
- v1beta2-rev1-2.1.2
+ v1beta2-rev1-4.0.0
compile
@@ -33,6 +33,13 @@
+
+ ${project.groupId}
+ gcloud-java-core
+ ${project.version}
+ test-jar
+ test
+
junit
junit
@@ -42,7 +49,7 @@
org.easymock
easymock
- 3.3
+ 3.4
test
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java
index d674a5e242ad..20c0b13e5001 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseEntity.java
@@ -33,6 +33,7 @@
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -128,61 +129,286 @@ public B remove(String name) {
return self();
}
+ /**
+ * Sets a property.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, Value> value) {
properties.put(name, value);
return self();
}
+ /**
+ * Sets a property of type {@link StringValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, String value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link StringValue}.
+ *
+ * @param name name of the property
+ * @param first the first string in the list
+ * @param second the second string in the list
+ * @param others other strings in the list
+ */
+ public B set(String name, String first, String second, String... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (String other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link LongValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, long value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link LongValue}.
+ *
+ * @param name name of the property
+ * @param first the first long in the list
+ * @param second the second long in the list
+ * @param others other longs in the list
+ */
+ public B set(String name, long first, long second, long... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (long other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link DoubleValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, double value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link DoubleValue}.
+ *
+ * @param name name of the property
+ * @param first the first double in the list
+ * @param second the second double in the list
+ * @param others other doubles in the list
+ */
+ public B set(String name, double first, double second, double... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (double other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link BooleanValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, boolean value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link BooleanValue}.
+ *
+ * @param name name of the property
+ * @param first the first boolean in the list
+ * @param second the second boolean in the list
+ * @param others other booleans in the list
+ */
+ public B set(String name, boolean first, boolean second, boolean... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (boolean other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link DateTimeValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, DateTime value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link DateTimeValue}.
+ *
+ * @param name name of the property
+ * @param first the first {@link DateTime} in the list
+ * @param second the second {@link DateTime} in the list
+ * @param others other {@link DateTime}s in the list
+ */
+ public B set(String name, DateTime first, DateTime second, DateTime... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (DateTime other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link KeyValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, Key value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link KeyValue}.
+ *
+ * @param name name of the property
+ * @param first the first {@link Key} in the list
+ * @param second the second {@link Key} in the list
+ * @param others other {@link Key}s in the list
+ */
+ public B set(String name, Key first, Key second, Key... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (Key other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link EntityValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, FullEntity> value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link EntityValue}.
+ *
+ * @param name name of the property
+ * @param first the first {@link FullEntity} in the list
+ * @param second the second {@link FullEntity} in the list
+ * @param others other entities in the list
+ */
+ public B set(String name, FullEntity> first, FullEntity> second, FullEntity>... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (FullEntity> other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@link ListValue}.
+ *
+ * @param name name of the property
+ * @param values list of values associated with the property
+ */
public B set(String name, List extends Value>> values) {
properties.put(name, of(values));
return self();
}
- public B set(String name, Value> value, Value>... other) {
- properties.put(name, of(value, other));
+ /**
+ * Sets a property of type {@link ListValue}.
+ *
+ * @param name name of the property
+ * @param first the first value in the list
+ * @param second the second value in the list
+ * @param others other values in the list
+ */
+ public B set(String name, Value> first, Value> second, Value>... others) {
+ properties.put(name, ListValue.builder().addValue(first).addValue(second, others).build());
return self();
}
+ /**
+ * Sets a property of type {@link BlobValue}.
+ *
+ * @param name name of the property
+ * @param value value associated with the property
+ */
public B set(String name, Blob value) {
properties.put(name, of(value));
return self();
}
+ /**
+ * Sets a list property containing elements of type {@link BlobValue}.
+ *
+ * @param name name of the property
+ * @param first the first {@link Blob} in the list
+ * @param second the second {@link Blob} in the list
+ * @param others other {@link Blob}s in the list
+ */
+ public B set(String name, Blob first, Blob second, Blob... others) {
+ List values = new LinkedList<>();
+ values.add(of(first));
+ values.add(of(second));
+ for (Blob other : others) {
+ values.add(of(other));
+ }
+ properties.put(name, of(values));
+ return self();
+ }
+
+ /**
+ * Sets a property of type {@code NullValue}.
+ *
+ * @param name name of the property
+ */
public B setNull(String name) {
properties.put(name, of());
return self();
@@ -348,8 +574,8 @@ public FullEntity getEntity(String name) {
* @throws ClassCastException if value is not a list of values
*/
@SuppressWarnings("unchecked")
- public List extends Value>> getList(String name) {
- return ((Value>>) getValue(name)).get();
+ public > List getList(String name) {
+ return (List) getValue(name).get();
}
/**
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java
index a8ad7d4e7734..4ab6f51b6767 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/BaseKey.java
@@ -157,6 +157,8 @@ public String kind() {
return leaf().kind();
}
+ abstract BaseKey parent();
+
@Override
public int hashCode() {
return Objects.hash(projectId(), namespace(), path());
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Batch.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Batch.java
index 75a5d1381403..5306a685195a 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Batch.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Batch.java
@@ -24,14 +24,14 @@
* to the Datastore upon {@link #submit}.
* A usage example:
* {@code
- * Entity entity1 = datastore.get(key1);
- * Batch batch = datastore.newBatch();
- * Entity entity2 = Entity.builder(key2).set("name", "John").build();
- * entity1 = Entity.builder(entity1).clear().setNull("bla").build();
- * Entity entity3 = Entity.builder(key3).set("title", "title").build();
- * batch.update(entity1);
- * batch.add(entity2, entity3);
- * batch.submit();
+ * Entity entity1 = datastore.get(key1);
+ * Batch batch = datastore.newBatch();
+ * Entity entity2 = Entity.builder(key2).set("name", "John").build();
+ * entity1 = Entity.builder(entity1).clear().setNull("bla").build();
+ * Entity entity3 = Entity.builder(key3).set("title", "title").build();
+ * batch.update(entity1);
+ * batch.add(entity2, entity3);
+ * batch.submit();
* }
*/
public interface Batch extends DatastoreBatchWriter {
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreImpl.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreImpl.java
index 92d18ed4787c..49a5728a4da9 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreImpl.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreImpl.java
@@ -26,7 +26,7 @@
import com.google.gcloud.RetryHelper;
import com.google.gcloud.RetryHelper.RetryHelperException;
import com.google.gcloud.RetryParams;
-import com.google.gcloud.spi.DatastoreRpc;
+import com.google.gcloud.datastore.spi.DatastoreRpc;
import com.google.protobuf.ByteString;
import java.util.Arrays;
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreOptions.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreOptions.java
index 2ec0f2be8f2b..db1a5f800ce8 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreOptions.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/DatastoreOptions.java
@@ -24,9 +24,9 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.gcloud.ServiceOptions;
-import com.google.gcloud.spi.DatastoreRpc;
-import com.google.gcloud.spi.DatastoreRpcFactory;
-import com.google.gcloud.spi.DefaultDatastoreRpc;
+import com.google.gcloud.datastore.spi.DatastoreRpc;
+import com.google.gcloud.datastore.spi.DatastoreRpcFactory;
+import com.google.gcloud.datastore.spi.DefaultDatastoreRpc;
import java.lang.reflect.Method;
import java.util.Iterator;
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java
index e6ae166dbf07..7c03b69d9f39 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/GqlQuery.java
@@ -43,27 +43,27 @@
* A usage example:
*
* When the type of the results is known the preferred usage would be:
- *
{@code
- * Query query =
- * Query.gqlQueryBuilder(Query.ResultType.ENTITY, "select * from kind").build();
- * QueryResults results = datastore.run(query);
- * while (results.hasNext()) {
- * Entity entity = results.next();
- * ...
- * }
+ * {@code
+ * Query query =
+ * Query.gqlQueryBuilder(Query.ResultType.ENTITY, "select * from kind").build();
+ * QueryResults results = datastore.run(query);
+ * while (results.hasNext()) {
+ * Entity entity = results.next();
+ * ...
+ * }
* }
*
* When the type of the results is unknown you can use this approach:
- *
{@code
- * Query> query = Query.gqlQueryBuilder("select __key__ from kind").build();
- * QueryResults> results = datastore.run(query);
- * if (Key.class.isAssignableFrom(results.resultClass())) {
- * QueryResults keys = (QueryResults) results;
- * while (keys.hasNext()) {
- * Key key = keys.next();
- * ...
- * }
+ * {@code
+ * Query> query = Query.gqlQueryBuilder("select __key__ from kind").build();
+ * QueryResults> results = datastore.run(query);
+ * if (Key.class.isAssignableFrom(results.resultClass())) {
+ * QueryResults keys = (QueryResults) results;
+ * while (keys.hasNext()) {
+ * Key key = keys.next();
+ * ...
* }
+ * }
* }
*
* @param the type of the result values this query will produce
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java
index 2ccd59e725a8..2192384ef70b 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/IncompleteKey.java
@@ -84,6 +84,29 @@ static IncompleteKey fromPb(DatastoreV1.Key keyPb) {
return new IncompleteKey(projectId, namespace, path);
}
+ /**
+ * Returns the key's parent.
+ */
+ @Override
+ public Key parent() {
+ List ancestors = ancestors();
+ if (ancestors.isEmpty()) {
+ return null;
+ }
+ PathElement parent = ancestors.get(ancestors.size() - 1);
+ Key.Builder keyBuilder;
+ if (parent.hasName()) {
+ keyBuilder = Key.builder(projectId(), parent.kind(), parent.name());
+ } else {
+ keyBuilder = Key.builder(projectId(), parent.kind(), parent.id());
+ }
+ String namespace = namespace();
+ if (namespace != null) {
+ keyBuilder.namespace(namespace);
+ }
+ return keyBuilder.ancestors(ancestors.subList(0, ancestors.size() - 1)).build();
+ }
+
public static Builder builder(String projectId, String kind) {
return new Builder(projectId, kind);
}
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java
index 41c7e82788b5..06282a2c79d1 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/ListValue.java
@@ -70,17 +70,16 @@ private Builder() {
super(ValueType.LIST);
}
- public Builder addValue(Value> value) {
+ private void addValueHelper(Value> value) {
// see datastore_v1.proto definition for list_value
Preconditions.checkArgument(value.type() != ValueType.LIST, "Cannot contain another list");
listBuilder.add(value);
- return this;
}
public Builder addValue(Value> first, Value>... other) {
- addValue(first);
+ addValueHelper(first);
for (Value> value : other) {
- addValue(value);
+ addValueHelper(value);
}
return this;
}
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java
index 15cca241e250..5892268f859c 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/StructuredQuery.java
@@ -46,35 +46,35 @@
* A usage example:
*
* A simple query that returns all entities for a specific kind
- *
{@code
- * Query query = Query.entityQueryBuilder().kind(kind).build();
- * QueryResults results = datastore.run(query);
- * while (results.hasNext()) {
- * Entity entity = results.next();
- * ...
- * }
+ * {@code
+ * Query query = Query.entityQueryBuilder().kind(kind).build();
+ * QueryResults results = datastore.run(query);
+ * while (results.hasNext()) {
+ * Entity entity = results.next();
+ * ...
+ * }
* }
*
* A simple key-only query of all entities for a specific kind
- *
{@code
- * Query keyOnlyQuery = Query.keyQueryBuilder().kind(KIND1).build();
- * QueryResults results = datastore.run(keyOnlyQuery);
- * ...
+ * {@code
+ * Query keyOnlyQuery = Query.keyQueryBuilder().kind(KIND1).build();
+ * QueryResults results = datastore.run(keyOnlyQuery);
+ * ...
* }
*
* A less trivial example of a projection query that returns the first 10 results
* of "age" and "name" properties (sorted and grouped by "age") with an age greater than 18
- *
{@code
- * Query query = Query.projectionEntityQueryBuilder()
- * .kind(kind)
- * .projection(Projection.property("age"), Projection.first("name"))
- * .filter(PropertyFilter.gt("age", 18))
- * .groupBy("age")
- * .orderBy(OrderBy.asc("age"))
- * .limit(10)
- * .build();
- * QueryResults results = datastore.run(query);
- * ...
+ * {@code
+ * Query query = Query.projectionEntityQueryBuilder()
+ * .kind(kind)
+ * .projection(Projection.property("age"), Projection.first("name"))
+ * .filter(PropertyFilter.gt("age", 18))
+ * .groupBy("age")
+ * .orderBy(OrderBy.asc("age"))
+ * .limit(10)
+ * .build();
+ * QueryResults results = datastore.run(query);
+ * ...
* }
*
* @param the type of the result values this query will produce
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Transaction.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Transaction.java
index 8089c0130f5d..78ee217f31e7 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Transaction.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/Transaction.java
@@ -30,21 +30,21 @@
* the Datastore upon {@code commit}.
* A usage example:
* {@code
- * Transaction transaction = datastore.newTransaction();
- * try {
- * Entity entity = transaction.get(key);
- * if (!entity.contains("last_name") || entity.isNull("last_name")) {
- * String[] name = entity.getString("name").split(" ");
- * entity = Entity.builder(entity).remove("name").set("first_name", name[0])
- * .set("last_name", name[1]).build();
- * transaction.update(entity);
- * transaction.commit();
- * }
- * } finally {
- * if (transaction.active()) {
- * transaction.rollback();
- * }
+ * Transaction transaction = datastore.newTransaction();
+ * try {
+ * Entity entity = transaction.get(key);
+ * if (!entity.contains("last_name") || entity.isNull("last_name")) {
+ * String[] name = entity.getString("name").split(" ");
+ * entity = Entity.builder(entity).remove("name").set("first_name", name[0])
+ * .set("last_name", name[1]).build();
+ * transaction.update(entity);
+ * transaction.commit();
* }
+ * } finally {
+ * if (transaction.active()) {
+ * transaction.rollback();
+ * }
+ * }
* }
*
* @see
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpc.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpc.java
similarity index 66%
rename from gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpc.java
rename to gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpc.java
index fd916e0a1c87..002078550d1f 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpc.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpc.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.gcloud.spi;
+package com.google.gcloud.datastore.spi;
import com.google.api.services.datastore.DatastoreV1.AllocateIdsRequest;
import com.google.api.services.datastore.DatastoreV1.AllocateIdsResponse;
@@ -35,16 +35,46 @@
*/
public interface DatastoreRpc {
- AllocateIdsResponse allocateIds(AllocateIdsRequest request) throws DatastoreException;
+ /**
+ * Sends an allocate IDs request.
+ *
+ * @throws DatastoreException upon failure
+ */
+ AllocateIdsResponse allocateIds(AllocateIdsRequest request);
+ /**
+ * Sends a begin transaction request.
+ *
+ * @throws DatastoreException upon failure
+ */
BeginTransactionResponse beginTransaction(BeginTransactionRequest request)
throws DatastoreException;
- CommitResponse commit(CommitRequest request) throws DatastoreException;
+ /**
+ * Sends a commit request.
+ *
+ * @throws DatastoreException upon failure
+ */
+ CommitResponse commit(CommitRequest request);
- LookupResponse lookup(LookupRequest request) throws DatastoreException;
+ /**
+ * Sends a lookup request.
+ *
+ * @throws DatastoreException upon failure
+ */
+ LookupResponse lookup(LookupRequest request);
- RollbackResponse rollback(RollbackRequest request) throws DatastoreException;
+ /**
+ * Sends a rollback request.
+ *
+ * @throws DatastoreException upon failure
+ */
+ RollbackResponse rollback(RollbackRequest request);
- RunQueryResponse runQuery(RunQueryRequest request) throws DatastoreException;
+ /**
+ * Sends a request to run a query.
+ *
+ * @throws DatastoreException upon failure
+ */
+ RunQueryResponse runQuery(RunQueryRequest request);
}
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpcFactory.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpcFactory.java
similarity index 90%
rename from gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpcFactory.java
rename to gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpcFactory.java
index 1815dda30f5d..0979b2203037 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DatastoreRpcFactory.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DatastoreRpcFactory.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.google.gcloud.spi;
+package com.google.gcloud.datastore.spi;
import com.google.gcloud.datastore.DatastoreOptions;
+import com.google.gcloud.spi.ServiceRpcFactory;
/**
* An interface for Datastore RPC factory.
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DefaultDatastoreRpc.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DefaultDatastoreRpc.java
similarity index 92%
rename from gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DefaultDatastoreRpc.java
rename to gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DefaultDatastoreRpc.java
index c82ff9689f68..093322fa4117 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/spi/DefaultDatastoreRpc.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/spi/DefaultDatastoreRpc.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.google.gcloud.spi;
+package com.google.gcloud.datastore.spi;
import com.google.api.services.datastore.DatastoreV1.AllocateIdsRequest;
import com.google.api.services.datastore.DatastoreV1.AllocateIdsResponse;
@@ -111,8 +111,7 @@ private static DatastoreException translate(
}
@Override
- public AllocateIdsResponse allocateIds(AllocateIdsRequest request)
- throws DatastoreException {
+ public AllocateIdsResponse allocateIds(AllocateIdsRequest request) {
try {
return client.allocateIds(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
@@ -121,8 +120,7 @@ public AllocateIdsResponse allocateIds(AllocateIdsRequest request)
}
@Override
- public BeginTransactionResponse beginTransaction(BeginTransactionRequest request)
- throws DatastoreException {
+ public BeginTransactionResponse beginTransaction(BeginTransactionRequest request) {
try {
return client.beginTransaction(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
@@ -131,7 +129,7 @@ public BeginTransactionResponse beginTransaction(BeginTransactionRequest request
}
@Override
- public CommitResponse commit(CommitRequest request) throws DatastoreException {
+ public CommitResponse commit(CommitRequest request) {
try {
return client.commit(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
@@ -140,7 +138,7 @@ public CommitResponse commit(CommitRequest request) throws DatastoreException {
}
@Override
- public LookupResponse lookup(LookupRequest request) throws DatastoreException {
+ public LookupResponse lookup(LookupRequest request) {
try {
return client.lookup(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
@@ -149,7 +147,7 @@ public LookupResponse lookup(LookupRequest request) throws DatastoreException {
}
@Override
- public RollbackResponse rollback(RollbackRequest request) throws DatastoreException {
+ public RollbackResponse rollback(RollbackRequest request) {
try {
return client.rollback(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
@@ -158,7 +156,7 @@ public RollbackResponse rollback(RollbackRequest request) throws DatastoreExcept
}
@Override
- public RunQueryResponse runQuery(RunQueryRequest request) throws DatastoreException {
+ public RunQueryResponse runQuery(RunQueryRequest request) {
try {
return client.runQuery(request);
} catch (com.google.api.services.datastore.client.DatastoreException ex) {
diff --git a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java
index 7c60da50b0b0..fdb6774f810f 100644
--- a/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java
+++ b/gcloud-java-datastore/src/main/java/com/google/gcloud/datastore/testing/LocalGcdHelper.java
@@ -17,6 +17,7 @@
package com.google.gcloud.datastore.testing;
import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
@@ -85,6 +86,7 @@ public class LocalGcdHelper {
private static final String GCLOUD = "gcloud";
private static final Path INSTALLED_GCD_PATH;
private static final String GCD_VERSION_PREFIX = "gcd-emulator ";
+ private static final double DEFAULT_CONSISTENCY = 0.9;
static {
INSTALLED_GCD_PATH = installedGcdPath();
@@ -398,9 +400,15 @@ public LocalGcdHelper(String projectId, int port) {
* All content is written to a temporary directory that will be deleted when
* {@link #stop()} is called or when the program terminates) to make sure that no left-over
* data from prior runs is used.
+ *
+ * @param consistency the fraction of job application attempts that will succeed, with 0.0
+ * resulting in no attempts succeeding, and 1.0 resulting in all attempts succeeding. Defaults
+ * to 0.9. Note that setting this to 1.0 may mask incorrect assumptions about the consistency
+ * of non-ancestor queries; non-ancestor queries are eventually consistent.
*/
- public void start() throws IOException, InterruptedException {
+ public void start(double consistency) throws IOException, InterruptedException {
// send a quick request in case we have a hanging process from a previous run
+ checkArgument(consistency >= 0.0 && consistency <= 1.0, "Consistency must be between 0 and 1");
sendQuitRequest(port);
// Each run is associated with its own folder that is deleted once test completes.
gcdPath = Files.createTempDirectory("gcd");
@@ -415,7 +423,7 @@ public void start() throws IOException, InterruptedException {
} else {
gcdExecutablePath = INSTALLED_GCD_PATH;
}
- startGcd(gcdExecutablePath);
+ startGcd(gcdExecutablePath, consistency);
}
private void downloadGcd() throws IOException {
@@ -453,7 +461,8 @@ private void downloadGcd() throws IOException {
}
}
- private void startGcd(Path executablePath) throws IOException, InterruptedException {
+ private void startGcd(Path executablePath, double consistency)
+ throws IOException, InterruptedException {
// cleanup any possible data for the same project
File datasetFolder = new File(gcdPath.toFile(), projectId);
deleteRecurse(datasetFolder.toPath());
@@ -486,7 +495,8 @@ private void startGcd(Path executablePath) throws IOException, InterruptedExcept
startProcess =
CommandWrapper.create()
.command(gcdAbsolutePath.toString(), "start", "--testing", "--allow_remote_shutdown",
- "--port=" + Integer.toString(port), projectId)
+ "--port=" + Integer.toString(port), "--consistency=" + Double.toString(consistency),
+ projectId)
.directory(gcdPath)
.start();
processReader = ProcessStreamReader.start(startProcess.getInputStream());
@@ -526,6 +536,7 @@ private static void extractFile(ZipInputStream zipIn, File filePath) throws IOEx
public static boolean sendQuitRequest(int port) {
StringBuilder result = new StringBuilder();
+ String shutdownMsg = "Shutting down local server";
try {
URL url = new URL("http", "localhost", port, "/_ah/admin/quit");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
@@ -537,13 +548,13 @@ public static boolean sendQuitRequest(int port) {
out.flush();
InputStream in = con.getInputStream();
int currByte = 0;
- while ((currByte = in.read()) != -1) {
+ while ((currByte = in.read()) != -1 && result.length() < shutdownMsg.length()) {
result.append(((char) currByte));
}
} catch (IOException ignore) {
// ignore
}
- return result.toString().startsWith("Shutting down local server");
+ return result.toString().startsWith(shutdownMsg);
}
public void stop() throws IOException, InterruptedException {
@@ -578,10 +589,10 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
});
}
- public static LocalGcdHelper start(String projectId, int port)
+ public static LocalGcdHelper start(String projectId, int port, double consistency)
throws IOException, InterruptedException {
LocalGcdHelper helper = new LocalGcdHelper(projectId, port);
- helper.start();
+ helper.start(consistency);
return helper;
}
@@ -593,7 +604,9 @@ public static void main(String... args) throws IOException, InterruptedException
switch (action) {
case "START":
if (!isActive(DEFAULT_PROJECT_ID, port)) {
- LocalGcdHelper helper = start(DEFAULT_PROJECT_ID, port);
+ double consistency = parsedArgs.get("consistency") == null
+ ? DEFAULT_CONSISTENCY : Double.parseDouble(parsedArgs.get("consistency"));
+ LocalGcdHelper helper = start(DEFAULT_PROJECT_ID, port, consistency);
try (FileWriter writer = new FileWriter(".local_gcd_helper")) {
writer.write(helper.gcdPath.toAbsolutePath().toString() + System.lineSeparator());
writer.write(Integer.toString(port));
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java
index 5ece01508d3a..a69ea5e20e3b 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseEntityTest.java
@@ -67,6 +67,16 @@ public void setUp() {
builder.set("list1", NullValue.of(), StringValue.of("foo"));
builder.set("list2", ImmutableList.of(LongValue.of(10), DoubleValue.of(2)));
builder.set("list3", Collections.singletonList(BooleanValue.of(true)));
+ builder.set(
+ "blobList", BLOB, Blob.copyFrom(new byte[] {3, 4}), Blob.copyFrom(new byte[] {5, 6}));
+ builder.set("booleanList", true, false, true);
+ builder.set("dateTimeList", DateTime.now(), DateTime.now(), DateTime.now());
+ builder.set("doubleList", 12.3, 4.56, .789);
+ builder.set("keyList", KEY, Key.builder("ds2", "k2", "n2").build(),
+ Key.builder("ds3", "k3", "n3").build());
+ builder.set("entityList", ENTITY, PARTIAL_ENTITY);
+ builder.set("stringList", "s1", "s2", "s3");
+ builder.set("longList", 1, 23, 456);
}
@Test
@@ -183,6 +193,17 @@ public void testGetList() throws Exception {
assertEquals(Boolean.TRUE, list.get(0).get());
entity = builder.set("list1", ListValue.of(list)).build();
assertEquals(list, entity.getList("list1"));
+ List> stringList = entity.getList("stringList");
+ assertEquals(
+ ImmutableList.of(StringValue.of("s1"), StringValue.of("s2"), StringValue.of("s3")),
+ stringList);
+ List> doubleList = entity.getList("doubleList");
+ assertEquals(
+ ImmutableList.of(DoubleValue.of(12.3), DoubleValue.of(4.56), DoubleValue.of(.789)),
+ doubleList);
+ List entityList = entity.getList("entityList");
+ assertEquals(
+ ImmutableList.of(EntityValue.of(ENTITY), EntityValue.of(PARTIAL_ENTITY)), entityList);
}
@Test
@@ -198,7 +219,9 @@ public void testGetBlob() throws Exception {
public void testNames() throws Exception {
Set names = ImmutableSet.builder()
.add("string", "stringValue", "boolean", "double", "long", "list1", "list2", "list3")
- .add("entity", "partialEntity", "null", "dateTime", "blob", "key")
+ .add("entity", "partialEntity", "null", "dateTime", "blob", "key", "blobList")
+ .add("booleanList", "dateTimeList", "doubleList", "keyList", "entityList", "stringList")
+ .add("longList")
.build();
BaseEntity entity = builder.build();
assertEquals(names, entity.names());
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java
index 8615ee025bd1..43db4695b191 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/BaseKeyTest.java
@@ -50,6 +50,11 @@ protected BaseKey build() {
protected Object fromPb(byte[] bytesPb) throws InvalidProtocolBufferException {
return null;
}
+
+ @Override
+ protected BaseKey parent() {
+ return null;
+ }
};
}
}
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreExceptionTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreExceptionTest.java
index 4d62224172f9..f7bdcb89bcec 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreExceptionTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreExceptionTest.java
@@ -78,7 +78,8 @@ public void testDatastoreException() throws Exception {
@Test
public void testTranslateAndThrow() throws Exception {
DatastoreException cause = new DatastoreException(503, "message", "UNAVAILABLE");
- RetryHelper.RetryHelperException exceptionMock = createMock(RetryHelper.RetryHelperException.class);
+ RetryHelper.RetryHelperException exceptionMock =
+ createMock(RetryHelper.RetryHelperException.class);
expect(exceptionMock.getCause()).andReturn(cause).times(2);
replay(exceptionMock);
try {
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreOptionsTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreOptionsTest.java
index 284a9d322793..1d188c7f4e94 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreOptionsTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreOptionsTest.java
@@ -22,9 +22,9 @@
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import com.google.gcloud.datastore.spi.DatastoreRpc;
+import com.google.gcloud.datastore.spi.DatastoreRpcFactory;
import com.google.gcloud.datastore.testing.LocalGcdHelper;
-import com.google.gcloud.spi.DatastoreRpc;
-import com.google.gcloud.spi.DatastoreRpcFactory;
import org.easymock.EasyMock;
import org.junit.Before;
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java
index e9eed027e8e0..e3829a2e71ce 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/DatastoreTest.java
@@ -31,14 +31,16 @@
import com.google.api.services.datastore.DatastoreV1.RunQueryRequest;
import com.google.api.services.datastore.DatastoreV1.RunQueryResponse;
import com.google.common.collect.Iterators;
+import com.google.common.collect.Lists;
+import com.google.gcloud.AuthCredentials;
import com.google.gcloud.RetryParams;
import com.google.gcloud.datastore.Query.ResultType;
import com.google.gcloud.datastore.StructuredQuery.OrderBy;
import com.google.gcloud.datastore.StructuredQuery.Projection;
import com.google.gcloud.datastore.StructuredQuery.PropertyFilter;
+import com.google.gcloud.datastore.spi.DatastoreRpc;
+import com.google.gcloud.datastore.spi.DatastoreRpcFactory;
import com.google.gcloud.datastore.testing.LocalGcdHelper;
-import com.google.gcloud.spi.DatastoreRpc;
-import com.google.gcloud.spi.DatastoreRpcFactory;
import com.google.protobuf.ByteString;
import org.easymock.EasyMock;
@@ -89,8 +91,8 @@ public class DatastoreTest {
FullEntity.builder(INCOMPLETE_KEY2).set("str", STR_VALUE).set("bool", BOOL_VALUE)
.set("list", LIST_VALUE1).build();
private static final FullEntity PARTIAL_ENTITY2 =
- FullEntity.builder(PARTIAL_ENTITY1).remove("str").set("bool", true).
- set("list", LIST_VALUE1.get()).build();
+ FullEntity.builder(PARTIAL_ENTITY1).remove("str").set("bool", true)
+ .set("list", LIST_VALUE1.get()).build();
private static final FullEntity PARTIAL_ENTITY3 =
FullEntity.builder(PARTIAL_ENTITY1).key(IncompleteKey.builder(PROJECT_ID, KIND3).build())
.build();
@@ -118,7 +120,7 @@ public class DatastoreTest {
@BeforeClass
public static void beforeClass() throws IOException, InterruptedException {
if (!LocalGcdHelper.isActive(PROJECT_ID, PORT)) {
- gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT);
+ gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 1.0);
}
}
@@ -127,6 +129,7 @@ public void setUp() {
options = DatastoreOptions.builder()
.projectId(PROJECT_ID)
.host("http://localhost:" + PORT)
+ .authCredentials(AuthCredentials.noAuth())
.retryParams(RetryParams.noRetries())
.build();
datastore = options.service();
@@ -471,9 +474,13 @@ public void testQueryPaginationWithLimit() throws DatastoreException {
EasyMock.expect(rpcFactoryMock.create(EasyMock.anyObject(DatastoreOptions.class)))
.andReturn(rpcMock);
List responses = buildResponsesForQueryPaginationWithLimit();
- for (int i = 0; i < responses.size(); i++) {
+ List endCursors = Lists.newArrayListWithCapacity(responses.size());
+ for (RunQueryResponse response : responses) {
EasyMock.expect(rpcMock.runQuery(EasyMock.anyObject(RunQueryRequest.class)))
- .andReturn(responses.get(i));
+ .andReturn(response);
+ if (response.getBatch().getMoreResults() != QueryResultBatch.MoreResultsType.NOT_FINISHED) {
+ endCursors.add(response.getBatch().getEndCursor());
+ }
}
EasyMock.replay(rpcFactoryMock, rpcMock);
Datastore mockDatastore = options.toBuilder()
@@ -483,6 +490,7 @@ public void testQueryPaginationWithLimit() throws DatastoreException {
.service();
int limit = 2;
int totalCount = 0;
+ Iterator cursorIter = endCursors.iterator();
StructuredQuery query = Query.entityQueryBuilder().limit(limit).build();
while (true) {
QueryResults results = mockDatastore.run(query);
@@ -492,6 +500,9 @@ public void testQueryPaginationWithLimit() throws DatastoreException {
resultCount++;
totalCount++;
}
+ assertTrue(cursorIter.hasNext());
+ Cursor expectedEndCursor = Cursor.copyFrom(cursorIter.next().toByteArray());
+ assertEquals(expectedEndCursor, results.cursorAfter());
if (resultCount < limit) {
break;
}
@@ -505,19 +516,20 @@ private List buildResponsesForQueryPaginationWithLimit() {
Entity entity4 = Entity.builder(KEY4).set("value", StringValue.of("value")).build();
Entity entity5 = Entity.builder(KEY5).set("value", "value").build();
datastore.add(ENTITY3, entity4, entity5);
+ DatastoreRpc datastoreRpc = datastore.options().rpc();
List responses = new ArrayList<>();
Query query = Query.entityQueryBuilder().build();
RunQueryRequest.Builder requestPb = RunQueryRequest.newBuilder();
query.populatePb(requestPb);
QueryResultBatch queryResultBatchPb = RunQueryResponse.newBuilder()
- .mergeFrom(((DatastoreImpl) datastore).runQuery(requestPb.build()))
+ .mergeFrom(datastoreRpc.runQuery(requestPb.build()))
.getBatch();
QueryResultBatch queryResultBatchPb1 = QueryResultBatch.newBuilder()
.mergeFrom(queryResultBatchPb)
.setMoreResults(QueryResultBatch.MoreResultsType.NOT_FINISHED)
.clearEntityResult()
.addAllEntityResult(queryResultBatchPb.getEntityResultList().subList(0, 1))
- .setEndCursor(queryResultBatchPb.getEntityResultList().get(0).getCursor())
+ .setEndCursor(ByteString.copyFromUtf8("a"))
.build();
responses.add(RunQueryResponse.newBuilder().setBatch(queryResultBatchPb1).build());
QueryResultBatch queryResultBatchPb2 = QueryResultBatch.newBuilder()
@@ -534,7 +546,7 @@ private List buildResponsesForQueryPaginationWithLimit() {
.setMoreResults(QueryResultBatch.MoreResultsType.MORE_RESULTS_AFTER_LIMIT)
.clearEntityResult()
.addAllEntityResult(queryResultBatchPb.getEntityResultList().subList(2, 4))
- .setEndCursor(queryResultBatchPb.getEntityResultList().get(3).getCursor())
+ .setEndCursor(ByteString.copyFromUtf8("b"))
.build();
responses.add(RunQueryResponse.newBuilder().setBatch(queryResultBatchPb3).build());
QueryResultBatch queryResultBatchPb4 = QueryResultBatch.newBuilder()
@@ -542,7 +554,7 @@ private List buildResponsesForQueryPaginationWithLimit() {
.setMoreResults(QueryResultBatch.MoreResultsType.NO_MORE_RESULTS)
.clearEntityResult()
.addAllEntityResult(queryResultBatchPb.getEntityResultList().subList(4, 5))
- .setEndCursor(queryResultBatchPb.getEntityResultList().get(4).getCursor())
+ .setEndCursor(ByteString.copyFromUtf8("c"))
.build();
responses.add(RunQueryResponse.newBuilder().setBatch(queryResultBatchPb4).build());
return responses;
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java
index 7edbf133d330..acd1dfd3c9e3 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/IncompleteKeyTest.java
@@ -17,29 +17,47 @@
package com.google.gcloud.datastore;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import org.junit.Before;
import org.junit.Test;
public class IncompleteKeyTest {
+ private static IncompleteKey pk1, pk2;
+ private static Key parent1;
+
+ @Before
+ public void setUp() {
+ pk1 = IncompleteKey.builder("ds", "kind1").build();
+ parent1 = Key.builder("ds", "kind2", 10).namespace("ns").build();
+ pk2 = IncompleteKey.builder(parent1, "kind3").build();
+ }
+
@Test
public void testBuilders() throws Exception {
- IncompleteKey pk1 = IncompleteKey.builder("ds", "kind1").build();
assertEquals("ds", pk1.projectId());
assertEquals("kind1", pk1.kind());
assertTrue(pk1.ancestors().isEmpty());
- Key parent = Key.builder("ds", "kind2", 10).build();
- IncompleteKey pk2 = IncompleteKey.builder(parent, "kind3").build();
assertEquals("ds", pk2.projectId());
assertEquals("kind3", pk2.kind());
- assertEquals(parent.path(), pk2.ancestors());
+ assertEquals(parent1.path(), pk2.ancestors());
assertEquals(pk2, IncompleteKey.builder(pk2).build());
IncompleteKey pk3 = IncompleteKey.builder(pk2).kind("kind4").build();
assertEquals("ds", pk3.projectId());
assertEquals("kind4", pk3.kind());
- assertEquals(parent.path(), pk3.ancestors());
+ assertEquals(parent1.path(), pk3.ancestors());
+ }
+
+ @Test
+ public void testParent() {
+ assertNull(pk1.parent());
+ assertEquals(parent1, pk2.parent());
+ Key parent2 = Key.builder("ds", "kind3", "name").namespace("ns").build();
+ IncompleteKey pk3 = IncompleteKey.builder(parent2, "kind3").build();
+ assertEquals(parent2, pk3.parent());
}
}
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java
index 40ea62c5a7e0..5d761a713506 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/LocalGcdHelperTest.java
@@ -49,7 +49,7 @@ public void testFindAvailablePort() {
@Test
public void testSendQuitRequest() throws IOException, InterruptedException {
- LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT);
+ LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 0.75);
assertTrue(LocalGcdHelper.sendQuitRequest(PORT));
long timeoutMillis = 30000;
long startTime = System.currentTimeMillis();
@@ -64,7 +64,7 @@ public void testSendQuitRequest() throws IOException, InterruptedException {
@Test
public void testStartStop() throws IOException, InterruptedException {
- LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT);
+ LocalGcdHelper gcdHelper = LocalGcdHelper.start(PROJECT_ID, PORT, 0.75);
assertFalse(LocalGcdHelper.isActive("wrong-project-id", PORT));
assertTrue(LocalGcdHelper.isActive(PROJECT_ID, PORT));
gcdHelper.stop();
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java
index 3976be2cc383..b9e78800ffab 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/SerializationTest.java
@@ -17,28 +17,17 @@
package com.google.gcloud.datastore;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
import com.google.api.services.datastore.DatastoreV1;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
import com.google.gcloud.AuthCredentials;
-import com.google.gcloud.RetryParams;
+import com.google.gcloud.BaseSerializationTest;
+import com.google.gcloud.Restorable;
import com.google.gcloud.datastore.StructuredQuery.CompositeFilter;
import com.google.gcloud.datastore.StructuredQuery.OrderBy;
import com.google.gcloud.datastore.StructuredQuery.Projection;
import com.google.gcloud.datastore.StructuredQuery.PropertyFilter;
-import org.junit.Test;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-
-public class SerializationTest {
+public class SerializationTest extends BaseSerializationTest {
private static final IncompleteKey INCOMPLETE_KEY1 =
IncompleteKey.builder("ds", "k").ancestors(PathElement.of("p", 1)).build();
@@ -113,83 +102,31 @@ public class SerializationTest {
.addValue(new NullValue())
.build();
private static final ProjectionEntity PROJECTION_ENTITY = ProjectionEntity.fromPb(ENTITY1.toPb());
+ private static final DatastoreException DATASTORE_EXCEPTION =
+ new DatastoreException(42, "message", "reason");
- @SuppressWarnings("rawtypes")
- private static final Multimap TYPE_TO_VALUES =
- ImmutableMultimap.builder()
- .put(ValueType.NULL, NULL_VALUE)
- .put(ValueType.KEY, KEY_VALUE)
- .put(ValueType.STRING, STRING_VALUE)
- .putAll(ValueType.ENTITY, EMBEDDED_ENTITY_VALUE1, EMBEDDED_ENTITY_VALUE2,
- EMBEDDED_ENTITY_VALUE3)
- .put(ValueType.LIST, LIST_VALUE)
- .put(ValueType.LONG, LONG_VALUE)
- .put(ValueType.DOUBLE, DOUBLE_VALUE)
- .put(ValueType.BOOLEAN, BOOLEAN_VALUE)
- .put(ValueType.DATE_TIME, DATE_AND_TIME_VALUE)
- .put(ValueType.BLOB, BLOB_VALUE)
- .put(ValueType.RAW_VALUE, RAW_VALUE)
- .build();
-
- @Test
- public void testServiceOptions() throws Exception {
+ @Override
+ protected java.io.Serializable[] serializableObjects() {
DatastoreOptions options = DatastoreOptions.builder()
.authCredentials(AuthCredentials.createForAppEngine())
.normalizeDataset(false)
.projectId("ds1")
.build();
- DatastoreOptions serializedCopy = serializeAndDeserialize(options);
- assertEquals(options, serializedCopy);
-
- options = options.toBuilder()
+ DatastoreOptions otherOptions = options.toBuilder()
.namespace("ns1")
- .retryParams(RetryParams.defaultInstance())
.authCredentials(null)
.force(true)
.build();
- serializedCopy = serializeAndDeserialize(options);
- assertEquals(options, serializedCopy);
- }
-
- @Test
- public void testValues() throws Exception {
- for (ValueType valueType : ValueType.values()) {
- for (Value> value : TYPE_TO_VALUES.get(valueType)) {
- Value> copy = serializeAndDeserialize(value);
- assertEquals(value, value);
- assertEquals(value, copy);
- assertNotSame(value, copy);
- assertEquals(copy, copy);
- assertEquals(value.get(), copy.get());
- }
- }
- }
-
- @Test
- public void testTypes() throws Exception {
- Serializable>[] types = { KEY1, KEY2, INCOMPLETE_KEY1, INCOMPLETE_KEY2, ENTITY1, ENTITY2,
- ENTITY3, EMBEDDED_ENTITY, PROJECTION_ENTITY, DATE_TIME1, BLOB1, CURSOR1, GQL1, GQL2,
- QUERY1, QUERY2, QUERY3};
- for (Serializable> obj : types) {
- Object copy = serializeAndDeserialize(obj);
- assertEquals(obj, obj);
- assertEquals(obj, copy);
- assertNotSame(obj, copy);
- assertEquals(copy, copy);
- }
+ return new java.io.Serializable[]{KEY1, KEY2, INCOMPLETE_KEY1, INCOMPLETE_KEY2, ENTITY1,
+ ENTITY2, ENTITY3, EMBEDDED_ENTITY, PROJECTION_ENTITY, DATE_TIME1, BLOB1, CURSOR1, GQL1,
+ GQL2, QUERY1, QUERY2, QUERY3, NULL_VALUE, KEY_VALUE, STRING_VALUE, EMBEDDED_ENTITY_VALUE1,
+ EMBEDDED_ENTITY_VALUE2, EMBEDDED_ENTITY_VALUE3, LIST_VALUE, LONG_VALUE, DOUBLE_VALUE,
+ BOOLEAN_VALUE, DATE_AND_TIME_VALUE, BLOB_VALUE, RAW_VALUE, DATASTORE_EXCEPTION, options,
+ otherOptions};
}
- private T serializeAndDeserialize(T obj)
- throws IOException, ClassNotFoundException {
- ByteArrayOutputStream bytes = new ByteArrayOutputStream();
- try (ObjectOutputStream output = new ObjectOutputStream(bytes)) {
- output.writeObject(obj);
- }
- try (ObjectInputStream input =
- new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray()))) {
- @SuppressWarnings("unchecked")
- T result = (T) input.readObject();
- return result;
- }
+ @Override
+ protected Restorable>[] restorableObjects() {
+ return null;
}
}
diff --git a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java
index b0d188cae16e..4b6589efd723 100644
--- a/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java
+++ b/gcloud-java-datastore/src/test/java/com/google/gcloud/datastore/StructuredQueryTest.java
@@ -40,7 +40,8 @@ public class StructuredQueryTest {
private static final Cursor END_CURSOR = Cursor.copyFrom(new byte[] {10});
private static final int OFFSET = 42;
private static final Integer LIMIT = 43;
- private static final Filter FILTER = CompositeFilter.and(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v"));
+ private static final Filter FILTER =
+ CompositeFilter.and(PropertyFilter.gt("p1", 10), PropertyFilter.eq("a", "v"));
private static final OrderBy ORDER_BY_1 = OrderBy.asc("p2");
private static final OrderBy ORDER_BY_2 = OrderBy.desc("p3");
private static final List ORDER_BY = ImmutableList.of(ORDER_BY_1, ORDER_BY_2);
diff --git a/gcloud-java-dns/README.md b/gcloud-java-dns/README.md
new file mode 100644
index 000000000000..d2e4c85b3b76
--- /dev/null
+++ b/gcloud-java-dns/README.md
@@ -0,0 +1,393 @@
+Google Cloud Java Client for DNS
+================================
+
+Java idiomatic client for [Google Cloud DNS] (https://cloud.google.com/dns/).
+
+[![Build Status](https://travis-ci.org/GoogleCloudPlatform/gcloud-java.svg?branch=master)](https://travis-ci.org/GoogleCloudPlatform/gcloud-java)
+[![Coverage Status](https://coveralls.io/repos/GoogleCloudPlatform/gcloud-java/badge.svg?branch=master)](https://coveralls.io/r/GoogleCloudPlatform/gcloud-java?branch=master)
+[![Maven](https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-dns.svg)]( https://img.shields.io/maven-central/v/com.google.gcloud/gcloud-java-dns.svg)
+[![Codacy Badge](https://api.codacy.com/project/badge/grade/9da006ad7c3a4fe1abd142e77c003917)](https://www.codacy.com/app/mziccard/gcloud-java)
+[![Dependency Status](https://www.versioneye.com/user/projects/56bd8ee72a29ed002d2b0969/badge.svg?style=flat)](https://www.versioneye.com/user/projects/56bd8ee72a29ed002d2b0969)
+
+- [Homepage] (https://googlecloudplatform.github.io/gcloud-java/)
+- [API Documentation] (http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/dns/package-summary.html)
+
+> Note: This client is a work-in-progress, and may occasionally
+> make backwards-incompatible changes.
+
+Quickstart
+----------
+If you are using Maven, add this to your pom.xml file
+```xml
+
+ com.google.gcloud
+ gcloud-java-dns
+ 0.1.5
+
+```
+If you are using Gradle, add this to your dependencies
+```Groovy
+compile 'com.google.gcloud:gcloud-java-dns:0.1.5'
+```
+If you are using SBT, add this to your dependencies
+```Scala
+libraryDependencies += "com.google.gcloud" % "gcloud-java-dns" % "0.1.5"
+```
+
+Example Application
+-------------------
+
+[`DnsExample`](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/dns/DnsExample.java)
+is a simple command line interface that provides some of Google Cloud DNS's functionality. Read
+more about using the application on the
+[`DnsExample` docs page](http://googlecloudplatform.github.io/gcloud-java/apidocs/?com/google/gcloud/examples/dns/DnsExample.html).
+
+Authentication
+--------------
+
+See the [Authentication](https://github.com/GoogleCloudPlatform/gcloud-java#authentication) section
+in the base directory's README.
+
+About Google Cloud DNS
+--------------------------
+
+[Google Cloud DNS][cloud-dns] is a scalable, reliable and managed authoritative Domain Name System
+(DNS) service running on the same infrastructure as Google. It has low latency, high availability
+and is a cost-effective way to make your applications and services available to your users.
+
+See the [Google Cloud DNS docs][dns-activate] for more details on how to activate
+Cloud DNS for your project.
+
+See the [``gcloud-java-dns`` API documentation][dns-api] to learn how to interact
+with the Cloud DNS using this client Library.
+
+Getting Started
+---------------
+#### Prerequisites
+For this tutorial, you will need a [Google Developers Console](https://console.developers.google.com/)
+project with the DNS API enabled. You will need to [enable billing](https://support.google.com/cloud/answer/6158867?hl=en)
+to use Google Cloud DNS. [Follow these instructions](https://cloud.google.com/docs/authentication#preparation)
+to get your project set up. You will also need to set up the local development environment by
+[installing the Google Cloud SDK](https://cloud.google.com/sdk/) and running the following commands
+in command line: `gcloud auth login` and `gcloud config set project [YOUR PROJECT ID]`.
+
+#### Installation and setup
+You'll need to obtain the `gcloud-java-dns` library. See the [Quickstart](#quickstart) section to
+add `gcloud-java-dns` as a dependency in your code.
+
+#### Creating an authorized service object
+To make authenticated requests to Google Cloud DNS, you must create a service object with
+credentials. You can then make API calls by calling methods on the DNS service object. The simplest
+way to authenticate is to use [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials).
+These credentials are automatically inferred from your environment, so you only need the following
+code to create your service object:
+
+```java
+import com.google.gcloud.dns.Dns;
+import com.google.gcloud.dns.DnsOptions;
+
+Dns dns = DnsOptions.defaultInstance().service();
+```
+
+For other authentication options, see the [Authentication](https://github.com/GoogleCloudPlatform/gcloud-java#authentication) page.
+
+#### Managing Zones
+Record sets in `gcloud-java-dns` are managed inside containers called "zones". `ZoneInfo` is a class
+which encapsulates metadata that describe a zone in Google Cloud DNS. `Zone`, a subclass of `ZoneInfo`, adds service-related
+functionality over `ZoneInfo`.
+
+*Important: Zone names must be unique to the project. If you choose a zone name that already
+exists within your project, you'll get a helpful error message telling you to choose another name. In the code below,
+replace "my-unique-zone" with a unique zone name. See more about naming rules [here](https://cloud.google.com/dns/api/v1/managedZones#name).*
+
+In this code snippet, we create a new zone to manage record sets for domain `someexampledomain.com.`
+
+*Important: The service may require that you verify ownership of the domain for which you are creating a zone.
+Hence, we recommend that you do so beforehand. You can verify ownership of
+a domain name [here](https://www.google.com/webmasters/verification/home). Note that Cloud DNS
+requires fully qualified domain names which must end with a period.*
+
+Add the following imports at the top of your file:
+
+```java
+import com.google.gcloud.dns.Zone;
+import com.google.gcloud.dns.ZoneInfo;
+```
+
+Then add the following code to create a zone.
+
+```java
+// Create a zone metadata object
+String zoneName = "my-unique-zone"; // Change this zone name which is unique within your project
+String domainName = "someexampledomain.com."; // Change this to a domain which you own
+String description = "This is a gcloud-java-dns sample zone.";
+ZoneInfo zoneInfo = ZoneInfo.of(zoneName, domainName, description);
+
+// Create zone in Google Cloud DNS
+Zone zone = dns.create(zoneInfo);
+System.out.printf("Zone was created and assigned ID %s.%n", zone.id());
+```
+
+You now have an empty zone hosted in Google Cloud DNS which is ready to be populated with
+record sets for domain name `someexampledomain.com.` Upon creating the zone, the cloud service
+assigned a set of DNS servers to host records for this zone and
+created the required SOA and NS records for the domain. The following snippet prints the list of servers
+assigned to the zone created above. First, import
+
+```java
+import java.util.List;
+```
+
+and then add
+
+```java
+// Print assigned name servers
+List nameServers = zone.nameServers();
+for(String nameServer : nameServers) {
+ System.out.println(nameServer);
+}
+```
+
+You can now instruct your domain registrar to [update your domain name servers] (https://cloud.google.com/dns/update-name-servers).
+As soon as this happens and the change propagates through cached values in DNS resolvers,
+all the DNS queries will be directed to and answered by the Google Cloud DNS service.
+
+#### Creating Record Sets
+Now that we have a zone, we can add some record sets. The record sets held within zones are
+modified by "change requests". In this example, we create and apply a change request to
+our zone that creates a record set of type A and points URL www.someexampledomain.com to
+IP address 12.13.14.15. Start by adding
+
+```java
+import com.google.gcloud.dns.ChangeRequestInfo;
+import com.google.gcloud.dns.RecordSet;
+
+import java.util.concurrent.TimeUnit;
+```
+
+and proceed with:
+
+```java
+// Prepare a www.someexampledomain.com. type A record set with ttl of 24 hours
+String ip = "12.13.14.15";
+RecordSet toCreate = RecordSet.builder("www." + zone.dnsName(), RecordSet.Type.A)
+ .ttl(24, TimeUnit.HOURS)
+ .addRecord(ip)
+ .build();
+
+// Make a change
+ChangeRequestInfo changeRequest = ChangeRequestInfo.builder().add(toCreate).build();
+
+// Build and apply the change request to our zone
+changeRequest = zone.applyChangeRequest(changeRequest);
+```
+
+The `addRecord` method of `RecordSet.Builder` accepts records in the form of
+strings. The format of the strings depends on the type of the record sets to be added.
+More information on the supported record set types and record formats can be found [here](https://cloud.google.com/dns/what-is-cloud-dns#supported_record_types).
+
+If you already have a record set, Cloud DNS will return an error upon an attempt to create a duplicate of it.
+You can modify the code above to create a record set or update it if it already exists by making the
+following adjustment in your imports
+
+```java
+import java.util.Iterator;
+```
+
+and in the code
+
+```java
+// Make a change
+ChangeRequestInfo.Builder changeBuilder = ChangeRequestInfo.builder().add(toCreate);
+
+// Verify the type A record does not exist yet.
+// If it does exist, we will overwrite it with our prepared record.
+Iterator recordSetIterator = zone.listRecordSets().iterateAll();
+while (recordSetIterator.hasNext()) {
+ RecordSet current = recordSetIterator.next();
+ if (toCreate.name().equals(current.name()) && toCreate.type().equals(current.type())) {
+ changeBuilder.delete(current);
+ }
+}
+
+// Build and apply the change request to our zone
+ChangeRequestInfo changeRequest = changeBuilder.build();
+zone.applyChangeRequest(changeRequest);
+```
+You can find more information about changes in the [Cloud DNS documentation] (https://cloud.google.com/dns/what-is-cloud-dns#cloud_dns_api_concepts).
+
+When the change request is applied, it is registered with the Cloud DNS service for processing. We
+can wait for its completion as follows:
+
+```java
+while (ChangeRequestInfo.Status.PENDING.equals(changeRequest.status())) {
+ try {
+ Thread.sleep(500L);
+ } catch (InterruptedException e) {
+ System.err.println("The thread was interrupted while waiting...");
+ }
+ changeRequest = dns.getChangeRequest(zone.name(), changeRequest.id());
+}
+System.out.println("The change request has been applied.");
+```
+
+Change requests are applied atomically to all the assigned DNS servers at once. Note that when this
+happens, it may still take a while for the change to be registered by the DNS cache resolvers.
+See more on this topic [here](https://cloud.google.com/dns/monitoring).
+
+#### Listing Zones and Record Sets
+Suppose that you have added more zones and record sets, and now you want to list them.
+First, import the following (unless you have done so in the previous section):
+
+```java
+import java.util.Iterator;
+```
+
+Then add the following code to list all your zones and record sets.
+
+```java
+// List all your zones
+Iterator zoneIterator = dns.listZones().iterateAll();
+int counter = 1;
+while (zoneIterator.hasNext()) {
+ System.out.printf("#%d.: %s%n%n", counter, zoneIterator.next());
+ counter++;
+}
+
+// List the record sets in a particular zone
+recordSetIterator = zone.listRecordSets().iterateAll();
+System.out.println(String.format("Record sets inside %s:", zone.name()));
+while (recordSetIterator.hasNext()) {
+ System.out.println(recordSetIterator.next());
+}
+```
+
+You can also list the history of change requests that were applied to a zone.
+First add:
+
+```java
+import java.util.ChangeRequest;
+```
+
+and then:
+
+```java
+
+// List the change requests applied to a particular zone
+Iterator changeIterator = zone.listChangeRequests().iterateAll();
+System.out.println(String.format("The history of changes in %s:", zone.name()));
+while (changeIterator.hasNext()) {
+ System.out.println(changeIterator.next());
+}
+```
+
+#### Deleting Zones
+
+If you no longer want to host a zone in Cloud DNS, you can delete it.
+First, you need to empty the zone by deleting all its records except for the default SOA and NS record sets.
+
+```java
+// Make a change for deleting the record sets
+changeBuilder = ChangeRequestInfo.builder();
+while (recordIterator.hasNext()) {
+ RecordSet current = recordIterator.next();
+ // SOA and NS records cannot be deleted
+ if (!RecordSet.Type.SOA.equals(current.type()) && !RecordSet.Type.NS.equals(current.type())) {
+ changeBuilder.delete(current);
+ }
+}
+
+// Build and apply the change request to our zone if it contains records to delete
+ChangeRequestInfo changeRequest = changeBuilder.build();
+if (!changeRequest.deletions().isEmpty()) {
+ changeRequest = dns.applyChangeRequest(zoneName, changeRequest);
+
+ // Wait for change to finish, but save data traffic by transferring only ID and status
+ Dns.ChangeRequestOption option =
+ Dns.ChangeRequestOption.fields(Dns.ChangeRequestField.STATUS);
+ while (ChangeRequestInfo.Status.PENDING.equals(changeRequest.status())) {
+ System.out.println("Waiting for change to complete. Going to sleep for 500ms...");
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ System.err.println("The thread was interrupted while waiting for change request to be "
+ + "processed.");
+ }
+
+ // Update the change, but fetch only change ID and status
+ changeRequest = dns.getChangeRequest(zoneName, changeRequest.id(), option);
+ }
+}
+
+// Delete the zone
+boolean result = dns.delete(zoneName);
+if (result) {
+ System.out.println("Zone was deleted.");
+} else {
+ System.out.println("Zone was not deleted because it does not exist.");
+}
+```
+
+#### Complete Source Code
+
+We composed some of the aforementioned snippets into complete executable code samples. In
+[CreateZones.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/dns/snippets/CreateZone.java)
+we create a zone. In [CreateOrUpdateRecordSets.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/dns/snippets/CreateOrUpdateRecordSets.java)
+we create a type A record set for a zone, or update an existing type A record set to a new IP address. We
+demonstrate how to delete a zone in [DeleteZone.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/dns/snippets/DeleteZone.java).
+Finally, in [ManipulateZonesAndRecordSets.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/dns/snippets/ManipulateZonesAndRecordSets.java)
+we assemble all the code snippets together and create zone, create or update a record set, list zones, list record sets, list changes, and
+delete a zone. The applications assume that they are running on Compute Engine or from your own desktop. To run any of these examples on App
+Engine, simply move the code from the main method to your application's servlet class and change the
+print statements to display on your webpage.
+
+Troubleshooting
+---------------
+
+To get help, follow the `gcloud-java` links in the `gcloud-*` [shared Troubleshooting document](https://github.com/GoogleCloudPlatform/gcloud-common/blob/master/troubleshooting/readme.md#troubleshooting).
+
+Java Versions
+-------------
+
+Java 7 or above is required for using this client.
+
+Testing
+-------
+
+This library has tools to help make tests for code using Cloud DNS.
+
+See [TESTING] to read more about testing.
+
+Versioning
+----------
+
+This library follows [Semantic Versioning] (http://semver.org/).
+
+It is currently in major version zero (``0.y.z``), which means that anything
+may change at any time and the public API should not be considered
+stable.
+
+Contributing
+------------
+
+Contributions to this library are always welcome and highly encouraged.
+
+See `gcloud-java`'s [CONTRIBUTING] documentation and the `gcloud-*` [shared documentation](https://github.com/GoogleCloudPlatform/gcloud-common/blob/master/contributing/readme.md#how-to-contribute-to-gcloud) for more information on how to get started.
+
+Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. See [Code of Conduct][code-of-conduct] for more information.
+
+License
+-------
+
+Apache 2.0 - See [LICENSE] for more information.
+
+
+[CONTRIBUTING]:https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/CONTRIBUTING.md
+[code-of-conduct]:https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/CODE_OF_CONDUCT.md#contributor-code-of-conduct
+[LICENSE]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/LICENSE
+[TESTING]: https://github.com/GoogleCloudPlatform/gcloud-java/blob/master/TESTING.md#testing-code-that-uses-storage
+[cloud-platform]: https://cloud.google.com/
+
+[cloud-dns]: https://cloud.google.com/dns/
+[dns-api]: http://googlecloudplatform.github.io/gcloud-java/apidocs/index.html?com/google/gcloud/dns/package-summary.html
+[dns-activate]:https://cloud.google.com/dns/getting-started#prerequisites
diff --git a/gcloud-java-dns/pom.xml b/gcloud-java-dns/pom.xml
new file mode 100644
index 000000000000..b55200b8fc7d
--- /dev/null
+++ b/gcloud-java-dns/pom.xml
@@ -0,0 +1,63 @@
+
+
+ 4.0.0
+ com.google.gcloud
+ gcloud-java-dns
+ jar
+ GCloud Java DNS
+
+ Java idiomatic client for Google Cloud DNS.
+
+
+ com.google.gcloud
+ gcloud-java-pom
+ 0.1.6-SNAPSHOT
+
+
+ gcloud-java-dns
+
+
+
+