/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.view.worker;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.core.change.ChangeEvent;
import com.opengamma.core.change.ChangeListener;
import com.opengamma.id.ObjectId;
/**
* Listens for changes to a known set of target resolutions. Changes to these resolutions may trigger recompilation of a view definition as the dependency graph based on the old resolutions may no
* longer be valid.
*/
public abstract class TargetResolverChangeListener implements ChangeListener {
private static final Logger s_logger = LoggerFactory.getLogger(TargetResolverChangeListener.class);
/**
* Marker for the state of watched targets.
*/
private static enum TargetState {
/**
* Notification of changes to the target are required, but it must be checked for any changes between when it was last queried and this state was stored. After such a check, the state may be
* changed to {@link #WAITING}.
*/
REQUIRED,
/**
* Notification of changes to the target are required, none have been received, and it will not be checked unless notified. After a change is received, the state may be changed to {@link #CHANGED}
* .
*/
WAITING,
/**
* Notification of changes to the target are required, at least one is pending, and it must now be checked. Before the check is made, the state may be changed to {@link #WAITING}.
*/
CHANGED
}
/**
* Map of target object identifiers to be monitored, to the monitoring state (see {@link TargetState} members).
*/
private final ConcurrentMap<ObjectId, TargetState> _targets = new ConcurrentHashMap<ObjectId, TargetState>();
/**
* Indicates that there may be at least one target in the {@link #_targets} map that has changed or needs a manual check.
*/
private volatile boolean _hasPending;
public void watch(final ObjectId identifier) {
final TargetState previous = _targets.putIfAbsent(identifier, TargetState.REQUIRED);
if (previous != TargetState.WAITING) {
_hasPending = true;
}
}
/**
* Tests if a change has been seen for an object, or if the object has not been monitored and a manual check is necessary. The state is cleared.
*
* @param identifier the object identifier to test, not null
* @return true if the object has changed, or was not being monitored, false otherwise
*/
public boolean isChanged(final ObjectId identifier) {
TargetState state = _targets.get(identifier);
if (state == TargetState.WAITING) {
s_logger.debug("No change to {}", identifier);
return false;
}
// Either new, or has changed; set to WAITING and return TRUE
s_logger.debug("New or changed identifier {} ({})", identifier, state);
_targets.put(identifier, TargetState.WAITING);
return true;
}
/**
* Prunes the watch list to only include the given identifiers.
*
* @param identifiers the identifiers to keep watching, not null and not containing null
*/
public void watchOnly(final Set<ObjectId> identifiers) {
_targets.keySet().retainAll(identifiers);
}
/**
* Clears out the watch list.
*/
public void watchNone() {
_targets.clear();
}
/**
* Indicates whether there are any objects that must be checked for updates. This is indicative only, and might not always be accurate.
*
* @return true if there might be, false otherwise
*/
public boolean hasChecksPending() {
return _hasPending;
}
/**
* Clears the flag that {@link #hasChecksPending} returns. Call this if there was an indication of pending checks but none were found.
*/
public void clearChecksPending() {
_hasPending = false;
}
/**
* Resets the object to its check-pending state.
*
* @param identifier the identifier to update, not null
*/
public void setChanged(final ObjectId identifier) {
_targets.put(identifier, TargetState.CHANGED);
}
protected abstract void onChanged();
// ChangeListener
@Override
public void entityChanged(final ChangeEvent event) {
final ObjectId oid = event.getObjectId();
TargetState state = _targets.get(oid);
if (state == null) {
return;
}
if ((state == TargetState.WAITING) || (state == TargetState.REQUIRED)) {
if (_targets.replace(oid, state, TargetState.CHANGED)) {
// If the state changed to anything else, we either don't need the notification or another change message overtook
// this one and a cycle has already been triggered.
s_logger.info("Received change notification for {}", oid);
_hasPending = true;
onChanged();
}
}
}
}