/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.brooklyn.core.mgmt.internal;
import java.util.Collection;
import java.util.Map;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.mgmt.SubscriptionContext;
import org.apache.brooklyn.api.mgmt.SubscriptionHandle;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.core.entity.AbstractEntity.BasicSubscriptionSupport;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.SetMultimap;
/**
* Tracks subscriptions associated that are registered with particular entities. Gives utilities for unsubscribing from all
* subscriptions on a given entity, etc.
*/
public class SubscriptionTracker {
// This class is thread-safe. All modifications to subscriptions are synchronized on the
// "subscriptions" field. However, calls to alien code (i.e. context.subscribe etc) is
// done without holding the lock.
//
// If two threads do subscribe() and unsubscribeAll() concurrently, then it's non-derministic
// whether the subscription will be in place at the end (but that's unavoidable). However, it
// is guaranteed that the internal state of the SubscriptionTracker will be consistent: if
// the "subscriptions" includes the new subscription then that subscription will really exist,
// and vice versa.
protected SubscriptionContext context;
private final SetMultimap<Entity, SubscriptionHandle> subscriptions = HashMultimap.create();
public SubscriptionTracker(SubscriptionContext subscriptionContext) {
this.context = subscriptionContext;
}
/** @see SubscriptionContext#subscribe(Entity, Sensor, SensorEventListener) */
public <T> SubscriptionHandle subscribe(Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
return subscribe(ImmutableMap.<String, Object>of(), producer, sensor, listener);
}
public <T> SubscriptionHandle subscribe(Map<String, ?> flags, Entity producer, Sensor<T> sensor, SensorEventListener<? super T> listener) {
SubscriptionHandle handle = context.subscribe(flags, producer, sensor, listener);
synchronized (subscriptions) {
subscriptions.put(producer, handle);
}
return handle;
}
/** @see SubscriptionContext#subscribeToChildren(Entity, Sensor, SensorEventListener) */
public <T> SubscriptionHandle subscribeToChildren(Entity parent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
SubscriptionHandle handle = context.subscribeToChildren(parent, sensor, listener);
synchronized (subscriptions) {
subscriptions.put(parent, handle);
}
return handle;
}
/**
* @see SubscriptionContext#subscribeToMembers(Group, Sensor, SensorEventListener)
*/
public <T> SubscriptionHandle subscribeToMembers(Group parent, Sensor<T> sensor, SensorEventListener<? super T> listener) {
SubscriptionHandle handle = context.subscribeToMembers(parent, sensor, listener);
synchronized (subscriptions) {
subscriptions.put(parent, handle);
}
return handle;
}
/**
* Unsubscribes the given producer.
*
* @see SubscriptionContext#unsubscribe(SubscriptionHandle)
*/
public boolean unsubscribe(Entity producer) {
Collection<SubscriptionHandle> handles;
synchronized (subscriptions) {
handles = subscriptions.removeAll(producer);
}
if (handles != null) {
for (SubscriptionHandle handle : handles) {
context.unsubscribe(handle);
}
return true;
}
return false;
}
/**
* Unsubscribes the given producer.
*
* @see SubscriptionContext#unsubscribe(SubscriptionHandle)
*/
public boolean unsubscribe(Entity producer, SubscriptionHandle handle) {
synchronized (subscriptions) {
subscriptions.remove(producer, handle);
}
return context.unsubscribe(handle);
}
/**
* Unsubscribes the given handle.
*
* It is (currently) more efficient to also pass in the producer -
* see {@link BasicSubscriptionSupport#unsubscribe(Entity, SubscriptionHandle)}
*
* @see SubscriptionContext#unsubscribe(SubscriptionHandle)
*/
public boolean unsubscribe(SubscriptionHandle handle) {
synchronized (subscriptions) {
subscriptions.values().remove(handle);
}
return context.unsubscribe(handle);
}
/**
* @return an ordered list of all subscription handles
*/
public Collection<SubscriptionHandle> getAllSubscriptions() {
synchronized (subscriptions) {
return ImmutableList.copyOf(subscriptions.values());
}
}
public void unsubscribeAll() {
Collection<SubscriptionHandle> subscriptionsSnapshot;
synchronized (subscriptions) {
subscriptionsSnapshot = ImmutableList.copyOf(subscriptions.values());
subscriptions.clear();
}
for (SubscriptionHandle s: subscriptionsSnapshot) {
context.unsubscribe(s);
}
}
}