/* * Copyright 2015 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * 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 org.hawkular.inventory.base; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import org.hawkular.inventory.api.Action; import org.hawkular.inventory.api.Interest; import org.hawkular.inventory.api.Log; import rx.Observable; import rx.Subscriber; import rx.functions.Action0; import rx.observers.SafeSubscriber; import rx.subjects.PublishSubject; import rx.subjects.Subject; /** * Hold the observables corresponding to different interests for being notified about on inventory. * * @author Lukas Krejci * @since 0.0.1 */ final class ObservableContext { private final Map<Interest<?, ?>, SubjectAndWrapper<?>> observables = new ConcurrentHashMap<>(); public <C> Observable<C> getObservableFor(Interest<C, ?> interest) { SubjectAndWrapper<C> sub = getSubjectAndWrapper(interest, true); return sub.wrapper; } public boolean isObserved(Interest<?, ?> interest) { return observables.containsKey(interest); } @SuppressWarnings("unchecked") public <C, T> Iterator<Subject<C, C>> matchingSubjects(Action<C, T> action, T object) { return observables.entrySet().stream().filter((e) -> e.getKey().matches(action, object)) .map((e) -> ((SubjectAndWrapper<C>) e.getValue()).subject).iterator(); } private <C> SubjectAndWrapper<C> getSubjectAndWrapper(Interest<C, ?> interest, boolean initialize) { @SuppressWarnings("unchecked") SubjectAndWrapper<C> sub = (SubjectAndWrapper<C>) observables.get(interest); if (initialize && sub == null) { SubscriptionTracker tracker = new SubscriptionTracker(() -> observables.remove(interest)); Subject<C, C> subject = PublishSubject.<C>create().toSerialized(); //error handling: //OperatorIgnoreError - in case subscribers and us run in the same thread, an error in the subscriber //may error out the whole observable, which is definitely NOT what we want. Observable<C> wrapper = null; wrapper = subject.lift(new OperatorIgnoreError<>()).doOnSubscribe(tracker.onSubscribe()) .doOnUnsubscribe(tracker.onUnsubscribe()); sub = new SubjectAndWrapper<>(subject, wrapper); observables.put(interest, sub); } return sub; } private static class SubscriptionTracker { private final AtomicLong counter = new AtomicLong(0); private final Runnable action; public SubscriptionTracker(Runnable action) { this.action = action; } public Action0 onSubscribe() { return counter::incrementAndGet; } public Action0 onUnsubscribe() { return () -> { if (counter.decrementAndGet() == 0) { action.run(); } }; } } private static class SubjectAndWrapper<T> { final Subject<T, T> subject; final Observable<T> wrapper; private SubjectAndWrapper(Subject<T, T> subject, Observable<T> wrapper) { this.subject = subject; this.wrapper = wrapper; } } private static final class OperatorIgnoreError<T> implements Observable.Operator<T, T> { @SuppressWarnings("unchecked") @Override public Subscriber<? super T> call(Subscriber<? super T> subscriber) { return new SafeSubscriber<T>(subscriber) { private boolean done = false; private final Subscriber<? super T> actual; { Subscriber<? super T> s = subscriber; while (s instanceof SafeSubscriber) { s = ((SafeSubscriber<? super T>) s).getActual(); } actual = s; } @Override public void onNext(T t) { if (done) { return; } try { actual.onNext(t); } catch (Exception e) { Log.LOGGER.debugf(e, "Subscriber %s failed to process %s.", actual, t); } } @Override protected void _onError(Throwable e) { done = true; super._onError(e); } }; } } }