
co.cask.cdap.common.conf.AbstractPropertyStore Maven / Gradle / Ivy
/*
* Copyright © 2014 Cask Data, Inc.
*
* 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 co.cask.cdap.common.conf;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import org.apache.twill.common.Cancellable;
import org.apache.twill.common.Threads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
/**
* Base implementation of {@link PropertyStore}.
*
* @param Type of property
*/
public abstract class AbstractPropertyStore implements PropertyStore {
private static final Logger LOG = LoggerFactory.getLogger(AbstractPropertyStore.class);
private final Multimap listeners;
private final Map propertyCache;
private final ExecutorService listenerExecutor;
private final AtomicBoolean closed;
protected AbstractPropertyStore() {
this.listeners = LinkedHashMultimap.create();
this.propertyCache = Maps.newHashMap();
this.listenerExecutor = Executors.newSingleThreadExecutor(Threads.createDaemonThreadFactory("property-store-%d"));
this.closed = new AtomicBoolean();
}
@Override
public final synchronized Cancellable addChangeListener(String name, PropertyChangeListener listener) {
ListenerCaller caller = new ListenerCaller(name, listener);
listeners.put(name, caller);
if (listenerAdded(name)) {
T property = propertyCache.get(name);
if (property != null) {
caller.onChange(name, property);
}
}
return caller;
}
@Override
public void close() {
if (closed.compareAndSet(false, true)) {
listenerExecutor.shutdownNow();
}
}
protected boolean isClosed() {
return closed.get();
}
/**
* Invoked when a new listener is added.
*
* @param name Name of the property with listener added
* @return {@code true} if notify the newly added listener with the cached value, {@code false} otherwise
*/
protected abstract boolean listenerAdded(String name);
/**
* Retrieves the cached property value.
*
* @param name Name of the property
* @return The cached value or {@code null} if no value is cached.
*/
@Nullable
protected final synchronized T getCached(String name) {
return propertyCache.get(name);
}
/**
* Updates property value and notify listeners.
*
* @param name Name of the property
* @param property New value of the property
*/
protected final synchronized void updateAndNotify(String name, T property) {
if (isClosed()) {
return;
}
if (property == null) {
propertyCache.remove(name);
} else {
propertyCache.put(name, property);
}
for (ListenerCaller caller : listeners.get(name)) {
caller.onChange(name, property);
}
}
/**
* Notifies listeners with error.
*
* @param name Name of the property
* @param failureCause The cause of the error.
*/
protected final synchronized void notifyError(String name, Throwable failureCause) {
if (isClosed()) {
return;
}
for (ListenerCaller caller : listeners.get(name)) {
caller.onError(name, failureCause);
}
}
/**
* Helper class to make calls to actual change listener through an executor.
*/
private final class ListenerCaller implements PropertyChangeListener, Cancellable {
private final String name;
private final PropertyChangeListener delegate;
private volatile boolean cancelled;
private ListenerCaller(String name, PropertyChangeListener delegate) {
this.delegate = delegate;
this.name = name;
}
@Override
public void onChange(final String name, final T property) {
if (cancelled) {
return;
}
try {
listenerExecutor.execute(new Runnable() {
@Override
public void run() {
if (cancelled) {
return;
}
try {
delegate.onChange(name, property);
} catch (Throwable t) {
invokeOnError(name, t);
}
}
});
} catch (RejectedExecutionException e) {
// Executor already shutdown, meaning the property store service was stopped already.
}
}
@Override
public void onError(final String name, final Throwable failureCause) {
if (cancelled) {
return;
}
listenerExecutor.execute(new Runnable() {
@Override
public void run() {
invokeOnError(name, failureCause);
}
});
}
@Override
public void cancel() {
cancelled = true;
synchronized (AbstractPropertyStore.this) {
listeners.remove(name, this);
}
}
private void invokeOnError(String name, Throwable failureCause) {
if (cancelled) {
return;
}
try {
delegate.onError(name, failureCause);
} catch (Throwable t) {
LOG.warn("Exception while calling PropertyChangeListener.onError", t);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy