diff --git a/README b/README deleted file mode 100644 index 8d4bd1c..0000000 --- a/README +++ /dev/null @@ -1,2 +0,0 @@ -Play for Work -============= diff --git a/examples/emm-notifications/.gitignore b/examples/emm-notifications/.gitignore new file mode 100644 index 0000000..0743fe9 --- /dev/null +++ b/examples/emm-notifications/.gitignore @@ -0,0 +1,6 @@ +.idea +target +.DS_Store +../.DS_Store +emm-notifications.iml +.idea diff --git a/examples/emm-notifications/README.md b/examples/emm-notifications/README.md new file mode 100644 index 0000000..ce120ce --- /dev/null +++ b/examples/emm-notifications/README.md @@ -0,0 +1,4 @@ +Play for Work EMM Push Notification +================================== + +Text goes here. diff --git a/examples/emm-notifications/google_settings.properties b/examples/emm-notifications/google_settings.properties new file mode 100644 index 0000000..36c9205 --- /dev/null +++ b/examples/emm-notifications/google_settings.properties @@ -0,0 +1,12 @@ +# NOT PUBLIC +# Values for our internal developer console project (enterprise-cloud-pub-sub) +ServiceAccountEmail=368628613713-t4hf388f34tdn5lhpdcu1qqfgio01626@developer.gserviceaccount.com + +# Download from here: https://pantheon.corp.google.com/project/enterprise-cloud-pub-sub/apiui/credential +ServiceAccountP12KeyFile=secret.p12 +ProjectName=enterprise-cloud-pub-sub +SubscriptionName=projects/enterprise-cloud-pub-sub/subscriptions/push-subscription +TopicName=projects/enterprise-cloud-pub-sub/topics/default-topic + +# Configured for the project mentioned above +PushEndpoint=e.r-k.co diff --git a/examples/emm-notifications/pom.xml b/examples/emm-notifications/pom.xml new file mode 100644 index 0000000..a32affa --- /dev/null +++ b/examples/emm-notifications/pom.xml @@ -0,0 +1,110 @@ + + 4.0.0 + com.google.android.work.emmnotifications + emm-notifications + jar + 1.0-SNAPSHOT + emm-notifications + http://maven.apache.org + + + + protoc-plugin + http://sergei-ivanov.github.com/maven-protoc-plugin/repo/releases/ + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.5 + 1.5 + + + + com.google.protobuf.tools + maven-protoc-plugin + 0.3.2 + + /usr/bin/protoc + + + + + compile + testCompile + + + + + + maven-assembly-plugin + + + + fully.qualified.MainClass + + + + jar-with-dependencies + + + + + + + + + junit + junit + 3.8.1 + test + + + com.google.protobuf + protobuf-java + 2.6.1 + + + junit + junit + 3.8.1 + test + + + com.google.apis + google-api-services-pubsub + LATEST + + + com.google.api-client + google-api-client + 1.18.0-rc + + + com.google.oauth-client + google-oauth-client-java6 + 1.15.0-rc + + + com.google.http-client + google-http-client-jackson2 + 1.19.0 + + + com.google.guava + guava + 18.0 + + + commons-cli + commons-cli + 1.2 + + + diff --git a/examples/emm-notifications/settings.properties b/examples/emm-notifications/settings.properties new file mode 100644 index 0000000..e165ba7 --- /dev/null +++ b/examples/emm-notifications/settings.properties @@ -0,0 +1,11 @@ +# Create new service account at https://console.developers.google.com +ServiceAccountEmail=changeme@gserviceaccount.com +ServiceAccountP12KeyFile=/path/to/key.p12 + +# This will be the name of the service account you will create +ProjectName=sample-project-name +SubscriptionName=projects/sample-project-name/subscriptions/default +TopicName=projects/sample-project-name/topics/default + +# Define new push endpoint in developer console project +PushEndpoint=sample.push.endpoint diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java new file mode 100644 index 0000000..ac42447 --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/FauxPublisher.java @@ -0,0 +1,89 @@ +package com.google.android.work.emmnotifications; + +import com.google.android.work.pubsub.EmmPubsub; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.services.pubsub.Pubsub; +import com.google.api.services.pubsub.model.PublishRequest; +import com.google.api.services.pubsub.model.PubsubMessage; +import com.google.api.services.pubsub.model.Topic; +import com.google.common.collect.ImmutableList; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.logging.Logger; + +/** + * This is a faux publisher which can be used to simulate messages that would be normally sent by + * Play for Work API + * + * To run this sample code: + *
    + *
  1. Modify settings.properties or specify a different file via DEVELOPER_CONSOLE_SETTINGS + * environment variable
  2. + *
  3. Build a deploy jar using mvn clean compile assembly:single
  4. + *
  5. Execute it as + * java -cp target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar \ + * com.google.android.work.emmnotifications.FauxPublisher
  6. + *
+ + */ +public class FauxPublisher { + + private static final Logger LOG = + Logger.getLogger(FauxPublisher.class.getName()); + + public static void main(String[] args) + throws IOException, GeneralSecurityException { + + Pubsub pubsubClient = ServiceAccountConfiguration.createPubsubClient( + Settings.getSettings().getServiceAccountEmail(), + Settings.getSettings().getServiceAccountP12KeyPath()); + String topicName = Settings.getSettings().getTopicName(); + + try { + Topic topic = pubsubClient + .projects() + .topics() + .get(topicName) + .execute(); + + LOG.info("Topic " + topicName + " exists: " + topic.toPrettyString()); + } catch (HttpResponseException e) { + if (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { + // Topic doesn't exist? + LOG.info("Topic " + topicName + " doesn't exists, creating it"); + pubsubClient + .projects() + .topics() + .create(topicName, new Topic()) + .execute(); + LOG.info("Topic " + topicName + " created"); + } + } + + ImmutableList.Builder listBuilder = ImmutableList.builder(); + + EmmPubsub.MdmPushNotification mdmPushNotification = EmmPubsub.MdmPushNotification.newBuilder() + .addProductApprovalEvent(EmmPubsub.ProductApprovalEvent.newBuilder() + .setApproved(false) + .setProductId("com.google.android.gms") + .setCommonEventInformation(EmmPubsub.MdmNotificationEnterpriseEventCommon.newBuilder() + .setEnterpriseId("12321321") + .setEventNotificationSentTimestamp("right now"))) + .build(); + + PublishRequest publishRequest = new PublishRequest() + .setMessages(ImmutableList.of(new PubsubMessage() + .encodeData(mdmPushNotification.toByteArray()))); + + LOG.info("Publishing a request: " + publishRequest.toPrettyString()); + + pubsubClient + .projects() + .topics() + .publish(topicName, publishRequest) + .execute(); + } + +} diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java new file mode 100644 index 0000000..f0d9087 --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/PushSubscriber.java @@ -0,0 +1,128 @@ +package com.google.android.work.emmnotifications; + +import com.google.android.work.pubsub.EmmPubsub; +import com.google.api.client.http.HttpResponseException; +import com.google.api.client.http.HttpStatusCodes; +import com.google.api.client.json.JsonParser; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.pubsub.Pubsub; +import com.google.api.services.pubsub.model.PubsubMessage; +import com.google.api.services.pubsub.model.PushConfig; +import com.google.api.services.pubsub.model.Subscription; +import com.google.common.io.CharStreams; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is a sample subscriber code. To run it you need to have an SSL endpoint configured + * and run this code either on port 443 (update below) on the server backing this endpoint or + * configure a reverse proxy from port 443 to 8093. + * + * Details: [link will be here] + * + * To run this sample code: + *
    + *
  1. Modify settings.properties or specify a different file via DEVELOPER_CONSOLE_SETTINGS + * environment variable
  2. + *
  3. Build a deploy jar using mvn clean compile assembly:single
  4. + *
  5. Execute it as + * java -cp target/emm-notifications-1.0-SNAPSHOT-jar-with-dependencies.jar \ + * com.google.android.work.emmnotifications.PushSubscriber
  6. + *
+ */ +public class PushSubscriber { + // this can be any port + private static final int PORT = 8093; + + private static final Logger LOG = Logger.getLogger(PushSubscriber.class.getName()); + public static final String MESSAGE_FIELD = "message"; + + public static void main(String[] args) throws Exception { + Pubsub client = ServiceAccountConfiguration.createPubsubClient( + Settings.getSettings().getServiceAccountEmail(), + Settings.getSettings().getServiceAccountP12KeyPath()); + + // First we check if subscription actually exists for this subscription name. + Subscription subscription = null; + + String topicName = Settings.getSettings().getTopicName(); + String subName = Settings.getSettings().getSubscriptionName(); + + LOG.info("Will be using topic name: " + topicName + ", subscription name: " + subName); + + try { + LOG.info("Trying to get subscription named " + subName); + subscription = client + .projects() + .subscriptions() + .get(subName) + .execute(); + + LOG.info("Will be re-using existing subscription: " + subscription.toPrettyString()); + } catch (HttpResponseException e) { + + // subscription not found + if (e.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND) { + LOG.info("Subscription doesn't exist, will try to create " + subName); + + // Creating subscription + subscription = client + .projects() + .subscriptions() + .create(subName, new Subscription() + .setTopic(topicName) // Name of the topic it subscribes to + .setAckDeadlineSeconds(600) + .setPushConfig(new PushConfig() + // FQDN with valid SSL certificate + .setPushEndpoint(Settings.getSettings().getPushEndpoint()))) + .execute(); + + LOG.info("Created: " + subscription.toPrettyString()); + } + } + + // Kicking off HttpServer which will listen on specified port and process all + // incoming push pub/sub notifications + HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0); + server.createContext("/", new HttpHandler() { + public void handle(HttpExchange httpExchange) throws IOException { + String rawRequest = CharStreams.toString( + new InputStreamReader(httpExchange.getRequestBody())); + LOG.info("Raw request: " + rawRequest); + + try { + // Note, that documentation says this is PubsubMessage, which it isn't + JsonParser parser = JacksonFactory.getDefaultInstance().createJsonParser(rawRequest); + parser.skipToKey(MESSAGE_FIELD); + + PubsubMessage message = parser.parseAndClose(PubsubMessage.class); + LOG.info("Pubsub message received: " + message.toPrettyString()); + + // Decoding Protocol Buffers message from array of bytes + EmmPubsub.MdmPushNotification mdmPushNotification = EmmPubsub.MdmPushNotification + .newBuilder() + .mergeFrom(message.decodeData()) + .build(); + + LOG.info("Message received: " + mdmPushNotification.toString()); + } catch (Throwable e) { + LOG.log(Level.WARNING, "Error occured when decoding message", e); + } + + // CloudPubSub will interpret 2XX as ACK, anything that isn't 2XX will trigger a retry + httpExchange.sendResponseHeaders(204, 0); + httpExchange.close(); + } + }); + + server.setExecutor(null); + server.start(); // Will keep running until killed + } +} diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java new file mode 100644 index 0000000..56b1488 --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/RetryHttpInitializerWrapper.java @@ -0,0 +1,96 @@ +package com.google.android.work.emmnotifications; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.HttpBackOffIOExceptionHandler; +import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler; +import com.google.api.client.http.HttpRequest; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpUnsuccessfulResponseHandler; +import com.google.api.client.util.ExponentialBackOff; +import com.google.api.client.util.Sleeper; +import com.google.common.base.Preconditions; +import com.google.common.io.CharStreams; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.Logger; + +/** + * RetryHttpInitializerWrapper will automatically retry upon RPC + * failures, preserving the auto-refresh behavior of the Google + * Credentials. + */ +public class RetryHttpInitializerWrapper implements HttpRequestInitializer { + + private static final Logger LOG = + Logger.getLogger(RetryHttpInitializerWrapper.class.getName()); + + // Intercepts the request for filling in the "Authorization" + // header field, as well as recovering from certain unsuccessful + // error codes wherein the Credential must refresh its token for a + // retry. + private final Credential wrappedCredential; + + // A sleeper; you can replace it with a mock in your test. + private final Sleeper sleeper; + + public RetryHttpInitializerWrapper(Credential wrappedCredential) { + this(wrappedCredential, Sleeper.DEFAULT); + } + + // Use only for testing. + RetryHttpInitializerWrapper( + Credential wrappedCredential, Sleeper sleeper) { + this.wrappedCredential = Preconditions.checkNotNull(wrappedCredential); + this.sleeper = sleeper; + } + + public void initialize(HttpRequest request) { + final HttpUnsuccessfulResponseHandler backoffHandler = + new HttpBackOffUnsuccessfulResponseHandler( + new ExponentialBackOff()) + .setSleeper(sleeper); + request.setInterceptor(wrappedCredential); + + request.setConnectTimeout(3 * 60000); // 3 minutes connect timeout + request.setReadTimeout(3 * 60000); // 3 minutes read timeout + + request.setUnsuccessfulResponseHandler( + new HttpUnsuccessfulResponseHandler() { + public boolean handleResponse( + HttpRequest request, + HttpResponse response, + boolean supportsRetry) throws IOException { + + LOG.info("RetryHandler: " + CharStreams.toString( + new InputStreamReader(response.getContent()))); + + if (wrappedCredential.handleResponse( + request, response, supportsRetry)) { + // If credential decides it can handle it, + // the return code or message indicated + // something specific to authentication, + // and no backoff is desired. + + LOG.info("Requested: " + request.getUrl().toString()); + return true; + } else if (backoffHandler.handleResponse( + request, response, supportsRetry)) { + + // Otherwise, we defer to the judgement of + // our internal backoff handler. + LOG.info("Retrying " + request.getUrl()); + return true; + } else { + return false; + } + } + }); + request.setIOExceptionHandler( + new HttpBackOffIOExceptionHandler(new ExponentialBackOff()) + .setSleeper(sleeper)); + } + + +} \ No newline at end of file diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java new file mode 100644 index 0000000..1124bb5 --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/ServiceAccountConfiguration.java @@ -0,0 +1,54 @@ +package com.google.android.work.emmnotifications; + +import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.services.pubsub.Pubsub; +import com.google.api.services.pubsub.PubsubScopes; + +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * Creates a Pubsub client with the service account. + */ +public class ServiceAccountConfiguration { + + private static final JsonFactory JSON_FACTORY = + JacksonFactory.getDefaultInstance(); + + public static Pubsub createPubsubClient(String serviceAccountEmail, String privateKeyFilePath) + throws IOException, GeneralSecurityException { + HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport(); + GoogleCredential credential = new GoogleCredential.Builder() + .setTransport(transport) + .setJsonFactory(JSON_FACTORY) + .setServiceAccountScopes(PubsubScopes.all()) + + // Obtain this from the "APIs & auth" -> "Credentials" + // section in the Google Developers Console: + // https://console.developers.google.com/ + // (and put the e-mail address into your system property obviously) + .setServiceAccountId(serviceAccountEmail) + + // Download this file from "APIs & auth" -> "Credentials" + // section in the Google Developers Console: + // https://console.developers.google.com/ + .setServiceAccountPrivateKeyFromP12File(new File(privateKeyFilePath)) + .build(); + + + // Please use custom HttpRequestInitializer for automatic + // retry upon failures. We provide a simple reference + // implementation in the "Retry Handling" section. + HttpRequestInitializer initializer = + new RetryHttpInitializerWrapper(credential); + return new Pubsub.Builder(transport, JSON_FACTORY, initializer) + .setApplicationName("PubSub Example") + .build(); + } +} \ No newline at end of file diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java new file mode 100644 index 0000000..e0273ce --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/Settings.java @@ -0,0 +1,157 @@ +package com.google.android.work.emmnotifications; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Properties; +import java.util.logging.Logger; + +import static com.google.api.client.util.Preconditions.checkArgument; +import static com.google.api.client.util.Preconditions.checkNotNull; + +/** + * Loads settings from settings.properties or file specified in DEVELOPER_CONSOLE_SETTINGS + * env variable. + */ +public class Settings { + private static final String EMAIL = "ServiceAccountEmail"; + private static final String KEY = "ServiceAccountP12KeyFile"; + private static final String TOPIC_NAME = "TopicName"; + private static final String SUBSCRIPTION_NAME = "SubscriptionName"; + private static final String PROJECT_NAME = "ProjectName"; + private static final String PUSH_ENDPOINT = "PushEndpoint"; + + private static final String EMAIL_DEFAULT = "changeme@gserviceaccount.com"; + private static final String KEY_DEFAULT = "/path/to/key.p12"; + private static final String TOPIC_NAME_DEFAULT = + "projects/sample-project-name/topics/default"; + private static final String SUBSCRIPTION_NAME_DEFAULT = + "projects/sample-project-name/subscriptions/default"; + private static final String PROJECT_NAME_DEFAULT = "sample-project-name"; + private static final String PUSH_ENDPOINT_DEFAULT = "sample.push.endpoint"; + + private static final Logger LOG = Logger.getLogger(Settings.class.getName()); + public static final String SETTINGS_ENV_VAR_NAME = "DEVELOPER_CONSOLE_SETTINGS"; + + private static Settings settings = null; + + // See https://cloud.google.com/pubsub/subscriber for more details + private final String topicName; + private final String subscriptionName; + + // Email address associated with your service account + // Obtain this from the "APIs & auth" -> "Credentials" + // section in the Google Developers Console: + // https://console.developers.google.com/ + // (and put the e-mail address into your system property obviously) + private final String serviceAccountEmail; + + // p12 file of your service account + // Download this file from "APIs & auth" -> "Credentials" + // section in the Google Developers Console: + // https://console.developers.google.com/ + private final String serviceAccountP12KeyPath; + + // Name of your developer console project + private final String projectName; + + // SSL WebHook enabled endpoint, see https://cloud.google.com/pubsub/prereqs#push_endpoints + private final String pushEndpoint; + + private Settings( + String serviceAccountEmail, + String serviceAccountP12KeyPath, + String topicName, + String subscriptionName, + String projectName, + String pushEndpoint) { + this.serviceAccountEmail = serviceAccountEmail; + this.serviceAccountP12KeyPath = serviceAccountP12KeyPath; + this.topicName = topicName; + this.subscriptionName = subscriptionName; + this.projectName = projectName; + this.pushEndpoint = pushEndpoint; + } + + /** + * Returns a singleton instance of Settings + * @throws IOException + */ + public static Settings getSettings() throws IOException { + if (settings != null) { + return settings; + } + + String settingsFilePath = "settings.properties"; + + String settingsEnvVariable = null; + if ((settingsEnvVariable = System.getenv(SETTINGS_ENV_VAR_NAME)) != null) { + settingsFilePath = settingsEnvVariable; + } + + Properties properties = new Properties(); + properties.load(new InputStreamReader(new FileInputStream(new File(settingsFilePath)))); + + String email = properties.getProperty(EMAIL); + String key = properties.getProperty(KEY); + String topicName = properties.getProperty(TOPIC_NAME); + String subscriptionName = properties.getProperty(SUBSCRIPTION_NAME); + String projectName = properties.getProperty(PROJECT_NAME); + String pushEndpoint = properties.getProperty(PUSH_ENDPOINT); + + verifyVariable(email, EMAIL, EMAIL_DEFAULT, settingsFilePath); + verifyVariable(key, KEY, KEY_DEFAULT, settingsFilePath); + verifyVariable(topicName, TOPIC_NAME, TOPIC_NAME_DEFAULT, settingsFilePath); + verifyVariable( + subscriptionName, + SUBSCRIPTION_NAME, + SUBSCRIPTION_NAME_DEFAULT, + settingsFilePath); + verifyVariable(projectName, PROJECT_NAME, PROJECT_NAME_DEFAULT, settingsFilePath); + verifyVariable(pushEndpoint, PUSH_ENDPOINT, PUSH_ENDPOINT_DEFAULT, settingsFilePath); + + return (settings = new Settings( + email, + key, + topicName, + subscriptionName, + projectName, + pushEndpoint)); + } + + private static void verifyVariable( + String variable, + String key, + String defaultValue, + String filePath) { + checkNotNull(variable, "%s must be set in %s", key, filePath); + checkArgument( + !variable.equals(defaultValue), + "You must specify non-default %s in %s", key, filePath); + } + + public String getServiceAccountEmail() { + return serviceAccountEmail; + } + + public String getServiceAccountP12KeyPath() { + return serviceAccountP12KeyPath; + } + + public String getSubscriptionName() { + return subscriptionName; + } + + public String getTopicName() { + return topicName; + } + + public String getProjectName() { + return projectName; + } + + public String getPushEndpoint() { + return pushEndpoint; + } +} diff --git a/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java new file mode 100644 index 0000000..3a26ce9 --- /dev/null +++ b/examples/emm-notifications/src/main/java/com/google/android/work/emmnotifications/notpublic/PullSubscriber.java @@ -0,0 +1,85 @@ +package com.google.android.work.emmnotifications.notpublic; + +import com.google.android.work.emmnotifications.ServiceAccountConfiguration; +import com.google.android.work.emmnotifications.Settings; +import com.google.api.client.http.HttpResponseException; +import com.google.api.services.pubsub.Pubsub; +import com.google.api.services.pubsub.model.AcknowledgeRequest; +import com.google.api.services.pubsub.model.PubsubMessage; +import com.google.api.services.pubsub.model.PullRequest; +import com.google.api.services.pubsub.model.PullResponse; +import com.google.api.services.pubsub.model.ReceivedMessage; +import com.google.api.services.pubsub.model.Subscription; +import com.google.common.collect.ImmutableList; + +import java.net.SocketTimeoutException; +import java.util.logging.Logger; + +/** + * ---- NOT PUBLIC CODE ---- + * + * Cloud PubSub backends currently 500's when trying to ACK the message therefore this code is NOT PUBLIC. + */ +public class PullSubscriber { + private static final Logger LOG = Logger.getLogger(PullSubscriber.class.getName()); + + public static void main(String[] args) throws Exception { + Pubsub client = ServiceAccountConfiguration.createPubsubClient( + Settings.getSettings().getServiceAccountEmail(), + Settings.getSettings().getServiceAccountP12KeyPath()); + String topicName = Settings.getSettings().getTopicName(); + String subName = Settings.getSettings().getSubscriptionName(); + Subscription subscription = null; + + try { + LOG.info("Trying to get subscription " + subName); + + subscription = client + .projects() + .subscriptions() + .get(subName) + .execute(); + + LOG.info("Got subscription back: " + subscription.toPrettyString()); + } catch (HttpResponseException e) { + throw e; + } + + PullRequest pullRequest = new PullRequest().setReturnImmediately(false).setMaxMessages(10); + + LOG.info("Will be polling: " + pullRequest.toPrettyString()); + + while (true) { + PullResponse response = null; + try { + response = client + .projects() + .subscriptions() + .pull(subName, pullRequest).execute(); + } catch (SocketTimeoutException e) { + // Something went wrong, try again in a bit + LOG.info("Timed out waiting for data, repeating in 1 second..."); + Thread.sleep(1000); + continue; + } + + ImmutableList.Builder ackIdsBuilder = ImmutableList.builder(); + for (ReceivedMessage msg : response.getReceivedMessages()) { + PubsubMessage message = msg.getMessage(); + String ackId = message.getMessageId(); + + LOG.info("Will be ack'ing " + ackId); + ackIdsBuilder.add(ackId); + } + + AcknowledgeRequest ack = new AcknowledgeRequest().setAckIds(ackIdsBuilder.build()); + client + .projects() + .subscriptions() + .acknowledge(subName, ack) + .execute(); + + Thread.sleep(500); + } + } +} diff --git a/examples/emm-notifications/src/main/proto/emm_pubsub.proto b/examples/emm-notifications/src/main/proto/emm_pubsub.proto new file mode 100644 index 0000000..3a2d640 --- /dev/null +++ b/examples/emm-notifications/src/main/proto/emm_pubsub.proto @@ -0,0 +1,32 @@ +syntax = "proto2"; + +option java_package = "com.google.android.work.pubsub"; + +// A notification delivered to an MDM to inform them of one or more event(s) +// relating to an enterprise. +message MdmPushNotification { + // Delivers notifications about changes to a product's approval status. + repeated ProductApprovalEvent product_approval_event = 1; +} + +// Common information applicable to multiple types of event contained within +// an MdmPushNotification message that concern a specific enterprise. +message MdmNotificationEnterpriseEventCommon { + // [Required] The ID of the enterprise that the event concerns. + optional string enterprise_id = 1; + + // [Required] The time when the event notification was published. + optional string event_notification_sent_timestamp = 2; +} + +// An event generated when a product's approval status is changed. +message ProductApprovalEvent { + // [Required] Common information regarding an enterprise event. + optional MdmNotificationEnterpriseEventCommon common_event_information = 1; + + // [Required] The ID of the product. + optional string product_id = 2; + + // [Required] Whether the product was approved or unapproved. + optional bool approved = 3; +} \ No newline at end of file