io.helidon.config.etcd.EtcdWatchPollingStrategy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-config-etcd Show documentation
Show all versions of helidon-config-etcd Show documentation
etcd config source implementation.
/*
* Copyright (c) 2017, 2018 Oracle and/or its affiliates. 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 io.helidon.config.etcd;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.helidon.common.reactive.Flow;
import io.helidon.common.reactive.SubmissionPublisher;
import io.helidon.config.ConfigException;
import io.helidon.config.ConfigHelper;
import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdEndpoint;
import io.helidon.config.etcd.internal.client.EtcdClient;
import io.helidon.config.etcd.internal.client.EtcdClientException;
import io.helidon.config.etcd.internal.client.EtcdUtils;
import io.helidon.config.spi.PollingStrategy;
/**
* Etcd watch strategy is based on etcd watch operation.
*/
public class EtcdWatchPollingStrategy implements PollingStrategy {
private static final Logger LOGGER = Logger.getLogger(EtcdWatchPollingStrategy.class.getName());
private final EtcdEndpoint endpoint;
private final EtcdClient etcdClient;
private final SubmissionPublisher ticksSubmitter;
private final Flow.Publisher ticksPublisher;
private EtcdWatchSubscriber etcdWatchSubscriber;
/**
* Creates polling strategy from etcd params.
*
* @param endpoint etcd remote descriptor
*/
public EtcdWatchPollingStrategy(EtcdEndpoint endpoint) {
this.endpoint = endpoint;
etcdClient = EtcdUtils.getClient(EtcdUtils.getClientClass(endpoint.getApi()), endpoint.getUri());
ticksSubmitter = new SubmissionPublisher<>(Runnable::run, //deliver events on current thread
1); //(almost) do not buffer events
ticksPublisher = ConfigHelper.suspendablePublisher(ticksSubmitter,
this::subscribePollingStrategy,
this::cancelPollingStrategy);
}
EtcdClient getEtcdClient() {
return etcdClient;
}
void subscribePollingStrategy() {
etcdWatchSubscriber = new EtcdWatchSubscriber();
try {
Flow.Publisher watchPublisher = getEtcdClient().watch(endpoint.getKey());
watchPublisher.subscribe(etcdWatchSubscriber);
} catch (EtcdClientException ex) {
ticksSubmitter.closeExceptionally(
new ConfigException(
String.format("Subscription on watching on '%s' key has failed. "
+ "Watching by '%s' polling strategy will not start.",
EtcdWatchPollingStrategy.this.endpoint.getKey(),
EtcdWatchPollingStrategy.this),
ex));
}
}
void cancelPollingStrategy() {
etcdWatchSubscriber.cancelSubscription();
etcdWatchSubscriber = null;
}
@Override
public Flow.Publisher ticks() {
return ticksPublisher;
}
private void fireEvent(Long item) {
ticksSubmitter.offer(
EtcdPollingEvent.from(item),
(subscriber, pollingEvent) -> {
LOGGER.log(Level.FINER, String.format("Event %s has not been delivered to %s.", pollingEvent, subscriber));
return false;
});
}
EtcdEndpoint getEtcdEndpoint() {
return endpoint;
}
/**
* An etcd polling event with the new content.
*/
private interface EtcdPollingEvent extends PollingEvent {
/**
* Returns etcd revision.
*
* @return etcd revision
*/
Long index();
static EtcdPollingEvent from(Long index) {
Instant timestamp = Instant.now();
return new EtcdPollingEvent() {
@Override
public Long index() {
return index;
}
@Override
public Instant getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return "EtcdPollingEvent @ " + timestamp + " # " + index;
}
};
}
}
/**
* {@link Flow.Subscriber} on {@link EtcdClient#watch(String)}.
*/
private class EtcdWatchSubscriber implements Flow.Subscriber {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
subscription.request(Long.MAX_VALUE);
}
@Override
public void onNext(Long item) {
EtcdWatchPollingStrategy.this.fireEvent(item);
}
@Override
public void onError(Throwable throwable) {
EtcdWatchPollingStrategy.this.ticksSubmitter
.closeExceptionally(new ConfigException(
String.format(
"Watching on '%s' key has failed. Watching by '%s' polling strategy will not continue. %s",
EtcdWatchPollingStrategy.this.endpoint.getKey(),
EtcdWatchPollingStrategy.this,
throwable.getLocalizedMessage()),
throwable));
}
@Override
public void onComplete() {
LOGGER.fine(String.format("Watching on '%s' key has completed. Watching by '%s' polling strategy will not continue.",
EtcdWatchPollingStrategy.this.endpoint.getKey(),
EtcdWatchPollingStrategy.this));
EtcdWatchPollingStrategy.this.ticksSubmitter.close();
}
private void cancelSubscription() {
if (subscription != null) {
subscription.cancel();
}
}
}
}