netflix.ocelli.CachingInstanceTransformer Maven / Gradle / Ivy
package netflix.ocelli;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Notification;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.subjects.BehaviorSubject;
import rx.subscriptions.Subscriptions;
/**
* Mapping function with cache to convert an Instance of one type to an Instance of
* another type. InstanceTransformer is meant to be used across multiple Instance streams
* when these streams may having overlapping instances so that only one Instance is
* created for each unique T. The returned Instance is a proxy to the single Instance
* so that it may be cleaned up when all the source Instance instances have been removed.
*
* @author elandau
*
* @param
* @param
*/
public class CachingInstanceTransformer implements Func1, Instance> {
private static final Logger LOG = LoggerFactory.getLogger(CachingInstanceTransformer.class);
public static CachingInstanceTransformer from(Func1 mapperFunc, Action1 shutdownFunc, Func1> createFunc) {
return new CachingInstanceTransformer(mapperFunc, shutdownFunc, createFunc);
}
private final Func1> createFunc;
private final Func1 mapperFunc;
private final Action1 shutdownFunc;
private final ConcurrentMap> cache = new ConcurrentHashMap>();
public static CachingInstanceTransformer create(Func1 mapperFunc, Action1 shutdownFunc, Func1> createFunc) {
return new CachingInstanceTransformer(mapperFunc, shutdownFunc, createFunc);
}
public static CachingInstanceTransformer create(Action1 shutdownFunc, Func1> createFunc) {
return create(
new Func1() {
@Override
public T call(T t1) {
return t1;
}
},
shutdownFunc,
createFunc);
}
public static CachingInstanceTransformer create(Func1> createFunc) {
return new CachingInstanceTransformer(
new Func1() {
@Override
public T call(T t1) {
return t1;
}
},
new Action1() {
@Override
public void call(T t1) {
}
},
createFunc);
}
public static CachingInstanceTransformer create() {
return new CachingInstanceTransformer(
new Func1() {
@Override
public T call(T t1) {
return t1;
}
},
new Action1() {
@Override
public void call(T t1) {
}
},
new Func1>() {
@Override
public Instance call(T t1) {
return MutableInstance.from(t1, BehaviorSubject.create(true));
}
});
}
/**
* Reference counted Instance that calls a shutdown action once all subscriptions
* have been removed.
*
* @author elandau
*
* @param
*/
private static class RefCountedInstance extends Instance {
public RefCountedInstance(final Instance delegate, final Action0 remove) {
super(delegate.getValue(), new OnSubscribe() {
private AtomicInteger refCount = new AtomicInteger();
@Override
public void call(Subscriber super Boolean> s) {
refCount.incrementAndGet();
delegate.subscribe(s);
s.add(Subscriptions.create(new Action0() {
@Override
public void call() {
if (refCount.decrementAndGet() == 0) {
remove.call();
}
}
}));
}
});
}
}
public CachingInstanceTransformer(Func1 mapperFunc, Action1 shutdownFunc, Func1> createFunc) {
this.createFunc = createFunc;
this.mapperFunc = mapperFunc;
this.shutdownFunc = shutdownFunc;
}
/**
* Internal method to create a new instance or get an existing one. The instance is
* removed from the cache once all subscription have been unsubscribed.
* @param member
* @return
*/
private Instance getOrCreateInstance(final Instance member) {
RefCountedInstance instance = cache.get(member.getValue());
if (instance == null) {
LOG.info("Created instance " + member);
final S newMember = mapperFunc.call(member.getValue());
instance = new RefCountedInstance(createFunc.call(newMember), new Action0() {
@Override
public void call() {
LOG.info("Destroy instance " + member);
cache.remove(member.getValue());
shutdownFunc.call(newMember);
}
});
RefCountedInstance existing = cache.putIfAbsent(member.getValue(), instance);
if (existing != null) {
instance.subscribe().unsubscribe();
instance = existing;
}
}
return instance;
}
@Override
public Instance call(final Instance member) {
final Instance instance = getOrCreateInstance(member);
return new Instance(instance.getValue(), new OnSubscribe() {
@Override
public void call(final Subscriber super Boolean> s) {
// AND the state of the source and transformed Instance (which may be attached to a
// failure detector) to determine the up state. The 'member' onCompleted event
// is used as the only indicator that the instance has been removed and the transformed
// Instance may be unsubscribed.
Observable.combineLatest(
member.materialize(), instance.materialize(),
new Func2, Notification, Notification>() {
@Override
public Notification call(Notification primary, Notification secondary) {
if (primary.isOnCompleted() || secondary.isOnCompleted()) {
return primary;
}
if (primary.isOnError()) {
return primary;
}
if (secondary.isOnError()) {
return secondary;
}
return Notification.createOnNext(primary.getValue() && secondary.getValue());
}
}
)
.dematerialize()
.subscribe(s);
}
});
}
}