All Downloads are FREE. Search and download functionalities are using the official Maven repository.

netflix.ocelli.CachingInstanceTransformer Maven / Gradle / Ivy

There is a newer version: 0.1.0-rc.2
Show newest version
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 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 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);
            }
        });
    }
}