() {
+ @Override
+ public Topic apply(com.google.pubsub.v1.Topic topicPb) {
+ return fromPb(pubsub, topicPb);
+ }
+ };
+ }
+}
diff --git a/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java
new file mode 100644
index 000000000000..37f4ae289b6f
--- /dev/null
+++ b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/TopicInfo.java
@@ -0,0 +1,119 @@
+/*
+ * 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.cloud.pubsub;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+/**
+ * Pub/Sub Topic information.
+ */
+public class TopicInfo implements Serializable {
+
+ private static final long serialVersionUID = -5907624842808725353L;
+
+ private final String name;
+
+ /**
+ * Builder for TopicInfo.
+ */
+ public abstract static class Builder {
+
+ public abstract Builder name(String name);
+
+ public abstract TopicInfo build();
+ }
+
+ static final class BuilderImpl extends Builder {
+
+ private String name;
+
+ BuilderImpl(String name) {
+ this.name = checkNotNull(name);
+ }
+
+ BuilderImpl(TopicInfo topicInfo) {
+ this.name = topicInfo.name;
+ }
+
+ @Override
+ public Builder name(String name) {
+ this.name = checkNotNull(name);
+ return this;
+ }
+
+ @Override
+ public TopicInfo build() {
+ return new TopicInfo(this);
+ }
+ }
+
+ TopicInfo(BuilderImpl builder) {
+ name = builder.name;
+ }
+
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ return Objects.equals(toPb(), ((TopicInfo) obj).toPb());
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("name", name)
+ .toString();
+ }
+
+ com.google.pubsub.v1.Topic toPb() {
+ return com.google.pubsub.v1.Topic.newBuilder().setName(name).build();
+ }
+
+ static TopicInfo fromPb(com.google.pubsub.v1.Topic topicPb) {
+ return builder(topicPb.getName()).build();
+ }
+
+ public Builder toBuilder() {
+ return new BuilderImpl(this);
+ }
+
+ public static TopicInfo of(String name) {
+ return builder(name).build();
+ }
+
+ public static Builder builder(String name) {
+ return new BuilderImpl(name);
+ }
+}
diff --git a/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java
new file mode 100644
index 000000000000..05dc8dcbf036
--- /dev/null
+++ b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/**
+ * A client to Google Cloud Pub/Sub.
+ *
+ * Here's a simple usage example for using gcloud-pubsub:
+ * todo: add example
+ *
{@code
+ * PubSub pubsub = PubSubOptions.defaultInstance().service();
+ * }
+ *
+ * @see Google Cloud Pub/Sub
+ */
+
+package com.google.cloud.pubsub;
diff --git a/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java
new file mode 100644
index 000000000000..ebe83841a4ac
--- /dev/null
+++ b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/DefaultPubSubRpc.java
@@ -0,0 +1,199 @@
+/*
+ * 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.cloud.pubsub.spi;
+
+import com.google.api.gax.core.ConnectionSettings;
+import com.google.api.gax.core.RetrySettings;
+import com.google.api.gax.grpc.ApiCallSettings;
+import com.google.api.gax.grpc.ApiException;
+import com.google.cloud.RetryParams;
+import com.google.cloud.pubsub.PubSubException;
+import com.google.cloud.pubsub.PubSubOptions;
+import com.google.cloud.pubsub.spi.v1.PublisherApi;
+import com.google.cloud.pubsub.spi.v1.PublisherSettings;
+import com.google.cloud.pubsub.spi.v1.SubscriberApi;
+import com.google.cloud.pubsub.spi.v1.SubscriberSettings;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.protobuf.Empty;
+import com.google.pubsub.v1.AcknowledgeRequest;
+import com.google.pubsub.v1.DeleteSubscriptionRequest;
+import com.google.pubsub.v1.DeleteTopicRequest;
+import com.google.pubsub.v1.GetSubscriptionRequest;
+import com.google.pubsub.v1.GetTopicRequest;
+import com.google.pubsub.v1.ListSubscriptionsRequest;
+import com.google.pubsub.v1.ListSubscriptionsResponse;
+import com.google.pubsub.v1.ListTopicSubscriptionsRequest;
+import com.google.pubsub.v1.ListTopicSubscriptionsResponse;
+import com.google.pubsub.v1.ListTopicsRequest;
+import com.google.pubsub.v1.ListTopicsResponse;
+import com.google.pubsub.v1.ModifyAckDeadlineRequest;
+import com.google.pubsub.v1.ModifyPushConfigRequest;
+import com.google.pubsub.v1.PublishRequest;
+import com.google.pubsub.v1.PublishResponse;
+import com.google.pubsub.v1.PullRequest;
+import com.google.pubsub.v1.PullResponse;
+import com.google.pubsub.v1.Subscription;
+import com.google.pubsub.v1.Topic;
+
+import org.joda.time.Duration;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import autovalue.shaded.com.google.common.common.collect.Sets;
+import io.grpc.Status.Code;
+
+public class DefaultPubSubRpc implements PubSubRpc {
+
+ private final PublisherApi publisherApi;
+ private final SubscriberApi subscriberApi;
+
+ public DefaultPubSubRpc(PubSubOptions options) throws IOException {
+ try {
+ // Provide (and use a common thread-pool).
+ // This depends on https://github.com/googleapis/gax-java/issues/73
+ PublisherSettings.Builder pbuilder = PublisherSettings.defaultInstance().toBuilder();
+ pbuilder.provideChannelWith(ConnectionSettings.newBuilder()
+ .provideCredentialsWith(options.authCredentials().credentials()).build());
+ pbuilder.applyToAllApiMethods(apiCallSettings(options));
+ publisherApi = PublisherApi.create(pbuilder.build());
+ SubscriberSettings.Builder sBuilder = SubscriberSettings.defaultInstance().toBuilder();
+ sBuilder.provideChannelWith(ConnectionSettings.newBuilder()
+ .provideCredentialsWith(options.authCredentials().credentials()).build());
+ sBuilder.applyToAllApiMethods(apiCallSettings(options));
+ subscriberApi = SubscriberApi.create(sBuilder.build());
+ } catch (Exception ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ private static ApiCallSettings.Builder apiCallSettings(PubSubOptions options) {
+ // TODO: specify timeout these settings:
+ // retryParams.retryMaxAttempts(), retryParams.retryMinAttempts()
+ RetryParams retryParams = options.retryParams();
+ final RetrySettings.Builder builder = RetrySettings.newBuilder()
+ .setTotalTimeout(Duration.millis(retryParams.totalRetryPeriodMillis()))
+ .setInitialRpcTimeout(Duration.millis(options.connectTimeout()))
+ .setRpcTimeoutMultiplier(1.5)
+ .setMaxRpcTimeout(Duration.millis(options.connectTimeout() + options.readTimeout()))
+ .setInitialRetryDelay(Duration.millis(retryParams.initialRetryDelayMillis()))
+ .setRetryDelayMultiplier(retryParams.retryDelayBackoffFactor())
+ .setMaxRetryDelay(Duration.millis(retryParams.maxRetryDelayMillis()));
+ return ApiCallSettings.newBuilder().setRetrySettingsBuilder(builder);
+ }
+
+ private static Future translate(ListenableFuture from, final boolean idempotent,
+ int... returnNullOn) {
+ final Set returnNullOnSet = Sets.newHashSetWithExpectedSize(returnNullOn.length);
+ for (int value : returnNullOn) {
+ returnNullOnSet.add(value);
+ }
+ return Futures.catching(from, ApiException.class, new Function() {
+ @Override
+ public V apply(ApiException exception) {
+ if (returnNullOnSet.contains(exception.getStatusCode().value())) {
+ return null;
+ }
+ throw new PubSubException(exception, idempotent);
+ }
+ });
+ }
+
+ @Override
+ public Future create(Topic topic) {
+ // TODO: it would be nice if we can get the idempotent inforamtion from the ApiCallSettings
+ // or from the exception
+ return translate(publisherApi.createTopicCallable().futureCall(topic), true);
+ }
+
+ @Override
+ public Future publish(PublishRequest request) {
+ return translate(publisherApi.publishCallable().futureCall(request), false);
+ }
+
+ @Override
+ public Future get(GetTopicRequest request) {
+ return translate(publisherApi.getTopicCallable().futureCall(request), true,
+ Code.NOT_FOUND.value());
+ }
+
+ @Override
+ public Future list(ListTopicsRequest request) {
+ // we should consider using gax PageAccessor once
+ // https://github.com/googleapis/gax-java/issues/74 is fixed
+ // Though it is a cleaner SPI without it, but PageAccessor is an interface
+ // and if it saves code we should not easily dismiss it.
+ return translate(publisherApi.listTopicsCallable().futureCall(request), true);
+ }
+
+ @Override
+ public Future list(ListTopicSubscriptionsRequest request) {
+ return translate(publisherApi.listTopicSubscriptionsCallable().futureCall(request), true);
+ }
+
+ @Override
+ public Future delete(DeleteTopicRequest request) {
+ // TODO: check if null is not going to work for Empty
+ return translate(publisherApi.deleteTopicCallable().futureCall(request), true,
+ Code.NOT_FOUND.value());
+ }
+
+ @Override
+ public Future create(Subscription subscription) {
+ return translate(subscriberApi.createSubscriptionCallable().futureCall(subscription), false);
+ }
+
+ @Override
+ public Future get(GetSubscriptionRequest request) {
+ return translate(subscriberApi.getSubscriptionCallable().futureCall(request), true,
+ Code.NOT_FOUND.value());
+ }
+
+ @Override
+ public Future list(ListSubscriptionsRequest request) {
+ return translate(subscriberApi.listSubscriptionsCallable().futureCall(request), true);
+ }
+
+ @Override
+ public Future delete(DeleteSubscriptionRequest request) {
+ return translate(subscriberApi.deleteSubscriptionCallable().futureCall(request), true,
+ Code.NOT_FOUND.value());
+ }
+
+ @Override
+ public Future modify(ModifyAckDeadlineRequest request) {
+ return translate(subscriberApi.modifyAckDeadlineCallable().futureCall(request), false);
+ }
+
+ @Override
+ public Future acknowledge(AcknowledgeRequest request) {
+ return translate(subscriberApi.acknowledgeCallable().futureCall(request), false);
+ }
+
+ @Override
+ public Future pull(PullRequest request) {
+ return translate(subscriberApi.pullCallable().futureCall(request), false);
+ }
+
+ @Override
+ public Future modify(ModifyPushConfigRequest request) {
+ return translate(subscriberApi.modifyPushConfigCallable().futureCall(request), false);
+ }
+}
diff --git a/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java
new file mode 100644
index 000000000000..8474ba042234
--- /dev/null
+++ b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpc.java
@@ -0,0 +1,72 @@
+/*
+ * 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.cloud.pubsub.spi;
+
+import com.google.protobuf.Empty;
+import com.google.pubsub.v1.AcknowledgeRequest;
+import com.google.pubsub.v1.DeleteSubscriptionRequest;
+import com.google.pubsub.v1.DeleteTopicRequest;
+import com.google.pubsub.v1.GetSubscriptionRequest;
+import com.google.pubsub.v1.GetTopicRequest;
+import com.google.pubsub.v1.ListSubscriptionsRequest;
+import com.google.pubsub.v1.ListSubscriptionsResponse;
+import com.google.pubsub.v1.ListTopicSubscriptionsRequest;
+import com.google.pubsub.v1.ListTopicSubscriptionsResponse;
+import com.google.pubsub.v1.ListTopicsRequest;
+import com.google.pubsub.v1.ListTopicsResponse;
+import com.google.pubsub.v1.ModifyAckDeadlineRequest;
+import com.google.pubsub.v1.ModifyPushConfigRequest;
+import com.google.pubsub.v1.PublishRequest;
+import com.google.pubsub.v1.PublishResponse;
+import com.google.pubsub.v1.PullRequest;
+import com.google.pubsub.v1.PullResponse;
+import com.google.pubsub.v1.Subscription;
+import com.google.pubsub.v1.Topic;
+
+import java.util.concurrent.Future;
+
+public interface PubSubRpc {
+
+ // in all cases root cause of ExecutionException is PubSubException
+ Future create(Topic topic);
+
+ Future publish(PublishRequest request);
+
+ Future get(GetTopicRequest request);
+
+ Future list(ListTopicsRequest request);
+
+ Future list(ListTopicSubscriptionsRequest request);
+
+ Future delete(DeleteTopicRequest request);
+
+ Future create(Subscription subscription);
+
+ Future get(GetSubscriptionRequest request);
+
+ Future list(ListSubscriptionsRequest request);
+
+ Future delete(DeleteSubscriptionRequest request);
+
+ Future modify(ModifyAckDeadlineRequest request);
+
+ Future acknowledge(AcknowledgeRequest request);
+
+ Future pull(PullRequest request);
+
+ Future modify(ModifyPushConfigRequest request);
+}
diff --git a/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java
new file mode 100644
index 000000000000..d3648a68399f
--- /dev/null
+++ b/gcloud-java-pubsub/src/main/java/com/google/cloud/pubsub/spi/PubSubRpcFactory.java
@@ -0,0 +1,27 @@
+/*
+ * 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.cloud.pubsub.spi;
+
+import com.google.cloud.pubsub.PubSubOptions;
+import com.google.cloud.spi.ServiceRpcFactory;
+
+/**
+ * An interface for Pub/Sub RPC factory.
+ * Implementation will be loaded via {@link java.util.ServiceLoader}.
+ */
+public interface PubSubRpcFactory extends ServiceRpcFactory {
+}