Java tutorial
/*- * -\-\- * Helios Services * -- * Copyright (C) 2016 Spotify AB * -- * 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.spotify.helios.servicescommon; import com.google.cloud.ByteArray; import com.google.cloud.pubsub.Message; import com.google.cloud.pubsub.PubSub; import com.google.common.annotations.VisibleForTesting; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.JdkFutureAdapters; import io.dropwizard.lifecycle.Managed; import java.time.Duration; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** An EventSender that publishes events to Google Cloud PubSub. */ public class GooglePubSubSender implements EventSender { private static final Logger log = LoggerFactory.getLogger(GooglePubSubSender.class); private final PubSub pubsub; private final String topicPrefix; private final HealthChecker healthchecker; public static GooglePubSubSender create(final PubSub pubSub, final String topicPrefix, final HealthChecker healthchecker) { return new GooglePubSubSender(pubSub, topicPrefix, healthchecker); } private GooglePubSubSender(final PubSub pubSub, final String topicPrefix, final HealthChecker healthchecker) { this.pubsub = pubSub; this.topicPrefix = topicPrefix; this.healthchecker = healthchecker; } @Override public void start() throws Exception { healthchecker.start(); } @Override public void stop() throws Exception { healthchecker.stop(); } @Override public void send(final String topic, final byte[] message) { final String combinedTopic = topicPrefix + topic; if (!healthchecker.isHealthy()) { log.warn( "will not publish message to pubsub topic={} as the pubsub client " + "appears to be unhealthy", combinedTopic); return; } try { Futures.addCallback( JdkFutureAdapters.listenInPoolThread( pubsub.publishAsync(combinedTopic, Message.of(ByteArray.copyFrom(message)))), new FutureCallback<String>() { @Override public void onSuccess(@Nullable final String ackId) { log.debug("Sent an event to Google PubSub, topic: {}, ack: {}", combinedTopic, ackId); } @Override public void onFailure(final Throwable th) { log.warn("Unable to send an event to Google PubSub, topic: {}", combinedTopic, th); } }); } catch (Exception e) { log.warn("Failed to publish Google PubSub message, topic: {}", combinedTopic, e); } } public interface HealthChecker extends Managed { boolean isHealthy(); } public static class DefaultHealthChecker implements HealthChecker { private final PubSub pubsub; private final String topic; private final ScheduledExecutorService executor; private final Duration healthcheckInterval; private AtomicBoolean healthy = new AtomicBoolean(false); public DefaultHealthChecker(final PubSub pubsub, final String topic, final ScheduledExecutorService executor, final Duration healthcheckInterval) { this.pubsub = pubsub; this.topic = topic; this.executor = executor; this.healthcheckInterval = healthcheckInterval; } @Override public void start() { final long millis = healthcheckInterval.toMillis(); executor.scheduleWithFixedDelay(this::checkHealth, 0, millis, TimeUnit.MILLISECONDS); } @Override public void stop() throws Exception { executor.shutdown(); } @Override public boolean isHealthy() { return healthy.get(); } @VisibleForTesting void checkHealth() { final boolean currentHealth = doCheckHealth(); final boolean oldHealth = healthy.getAndSet(currentHealth); // only log that it is healthy on a state change to avoid repeating this message in the logs // on every interval if (currentHealth && !oldHealth) { log.info("successfully checked if pubsub topic {} exists - this instance is now healthy", topic); } } private boolean doCheckHealth() { try { // perform a blocking call to see if we can connect to pubsub at all // if the topic does not exist, getTopic() returns null and does not throw an exception pubsub.getTopic(topic); return true; } catch (RuntimeException ex) { // PubSubException is an instance of RuntimeException, catch any other subtypes too log.warn("caught exception checking if pubsub topic {} exists. " + "Publishing to pubsub will be disabled until connectivity is restored " + "(next check is in {})", topic, healthcheckInterval, ex); return false; } } } }