org.snmp4j.agent.DefaultMOServer Maven / Gradle / Ivy
/*_############################################################################
_##
_## SNMP4J-Agent 3 - DefaultMOServer.java
_##
_## Copyright (C) 2005-2018 Frank Fock (SNMP4J.org)
_##
_## 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.snmp4j.agent;
import java.util.*;
import java.util.Map.Entry;
import org.snmp4j.agent.mo.*;
import org.snmp4j.agent.mo.lock.DefaultMOLockStrategy;
import org.snmp4j.agent.mo.lock.LockRequest;
import org.snmp4j.agent.mo.lock.MOLockStrategy;
import org.snmp4j.agent.request.SubRequest;
import org.snmp4j.log.*;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.*;
/**
* The default MO server implementation uses a sorted map for the managed object
* registry.
*
* @author Frank Fock
* @version 3.1.0
*/
public class DefaultMOServer implements MOServer {
private static final LogAdapter logger =
LogFactory.getLogger(DefaultMOServer.class);
private Set contexts;
private SortedMap> registry;
private Map, Lock> lockList;
private Map, List> lookupListener;
private transient List contextListeners;
private UpdateStrategy updateStrategy;
private MOLockStrategy lockStrategy = new DefaultMOLockStrategy();
public DefaultMOServer() {
this.registry = new TreeMap<>(new MOScopeComparator());
this.contexts = new LinkedHashSet<>(10);
this.lockList = new Hashtable<>(10);
}
@Override
@SuppressWarnings("rawtypes")
public ManagedObject lookup(MOQuery query) {
return lookup(query, false, null,
new MOServerLookupEvent(this, null, query, MOServerLookupEvent.IntendedUse.undefined),
ManagedObject.class);
}
/**
* Lookup the first (lexicographically ordered) managed object that matches
* the supplied query and implements the given {@link ManagedObject} class.
* Locking will be performed according to the set {@link MOLockStrategy} before the lookup
* listener is fired.
* CAUTION: To make sure that the acquired lock is released after the
* using of the managed object has been finished, the {@link #unlock(Object, ManagedObject)}
* method must be called then.
*
* @param query
* a {@code MOQuery} instance.
* @param lockRequest
* the {@link LockRequest} that holds the lock owner and the timeout for
* acquiring a lock and returns whether a lock has been acquired or not
* on behalf of this lookup operation.
* @param lookupEvent
* provides additional information about the intended use and optionally a callback to be informed about
* the completion of the use, including a reference to its result.
* @param managedObjectType
* the {@link ManagedObject} type filter. Only objects of this type will be looked up and returned.
*
* @return the {@code ManagedObject} that matches the query and
* {@code null} if no such object exists.
* @since 3.1
*/
@Override
public >
MO lookup(MOQuery query, LockRequest lockRequest, MOServerLookupEvent lookupEvent, Class managedObjectType) {
return lookup(query, false, lockRequest, lookupEvent, managedObjectType);
}
/**
* Return the locking strategy for this server. The strategy defines if a lock is acquired before
* a looked up {@link ManagedObject} is returned (and before the corresponding lookup event is being fired) or not.
*
* @return the managed object locking strategy instance used by this server. If {@code null}, no locking is performed
* at all (which is only recommended for static content servers).
* @since 2.4.0
*/
public MOLockStrategy getLockStrategy() {
return lockStrategy;
}
/**
* Sets the lock strategy for this server. The strategy defines if a lock is acquired before
* a looked up {@link ManagedObject} is returned (and before the corresponding lookup event is being fired) or not.
* By default, only write access needs a lock.
*
* @param lockStrategy
* a {@link MOLockStrategy} instance or {@code null} to suppress any locking.
*
* @since 2.4.0
*/
public void setLockStrategy(MOLockStrategy lockStrategy) {
this.lockStrategy = lockStrategy;
}
private > MO lookup(MOQuery query,
boolean specificRegistrationsOnly, LockRequest lockRequest,
MOServerLookupEvent event, Class managedObjectType) {
SortedMap> scope = registry.tailMap(query);
boolean timedOut = false;
if (lockRequest != null) {
lockRequest.setLockRequestStatus(LockRequest.LockStatus.notRequired);
}
for (Entry> entry : scope.entrySet()) {
MOScope key = entry.getKey();
if (!MOScopeComparator.isQueryContextMatching(query, key)) {
continue;
}
if ((specificRegistrationsOnly) &&
(!(key instanceof MOContextScope) || (((MOContextScope) key).getContext() == null))) {
continue;
}
ManagedObject> managedObject = entry.getValue();
MOScope moScope = managedObject.getScope();
if ((managedObjectType.isInstance(managedObject)) && query.getScope().isOverlapping(moScope)) {
MO mo = managedObjectType.cast(managedObject);
event.setLookupResult(mo);
fireQueryEvent(mo, event);
// apply locking if needed
if ((lockStrategy != null) && (lockRequest != null) && (lockRequest.getLockOwner() != null) &&
lockStrategy.isLockNeeded(mo, query)) {
if (!lock(lockRequest.getLockOwner(), mo, lockRequest.getTimeoutMillis())) {
timedOut = true;
continue;
} else {
lockRequest.setLockRequestStatus((timedOut) ?
LockRequest.LockStatus.lockedAfterTimeout : LockRequest.LockStatus.locked);
}
}
if (mo instanceof UpdatableManagedObject) {
checkForUpdate((UpdatableManagedObject) mo, query);
}
if (query.matchesQuery(mo)) {
event.setLookupResult(mo);
fireLookupEvent(mo, event);
return mo;
} else if (lockRequest != null) {
unlock(lockRequest.getLockOwner(), mo);
}
}
}
if (timedOut) {
lockRequest.setLockRequestStatus(LockRequest.LockStatus.lockTimedOut);
}
return null;
}
/**
* Checks {@link #updateStrategy} whether the queried managed object needs
* to be updated. This method is called on behalf of
* {@link #lookup(MOQuery query)} after {@link #fireQueryEvent} and before
* {@link #fireLookupEvent} is being called.
*
* @param mo
* an UpdatableManagedObject instance.
* @param query
* the query that is interested in content of {@code mo}.
*
* @since 1.2
*/
protected void checkForUpdate(UpdatableManagedObject> mo, MOQuery query) {
if (updateStrategy != null) {
if (updateStrategy.isUpdateNeeded(this, mo, query)) {
if (logger.isDebugEnabled()) {
logger.debug("Updating UpdatableManagedObject " + mo +
" on behalf of query " + query);
}
mo.update(query);
}
}
}
/**
* Returns the {@code ManagedObject} with the specified {@code OID}
* as ID returned by {@link RegisteredManagedObject#getID()} or the lower bound
* (regardless whether the ManagedObject's scope includes it or not)
* when registered in the supplied context.
*
* Note: The query used to lookup the managed object will indicate an intended
* read-only access for the {@link MOServerLookupEvent}s fired on behalf of
* this method.
*
* @param key
* the OID identifying the key (lower bound) of the
* {@code ManagedObject}.
* @param context
* the optional context to look in. A {@code null} value searches
* in all contexts.
* @param fireLookupEvents
* if {@code true} lookup and query events will be fired as if the managed
* objects has been looked up by {@link #lookup(MOQuery)}. In addition, if
* the looked up managed object is an {@link UpdatableManagedObject} it will be locked
* if the lock strategy of this server requires it. The lock is active until the look up
* events have been fired completely. The lock operation waits without timeout for the lock
* to become available.
* Otherwise, no events will be fired at all.
*
* @return the {@code ManagedObject} instance or {@code null} if such
* an instance does not exists.
* @since 2.3
*/
public ManagedObject> getManagedObject(OID key, OctetString context, boolean fireLookupEvents) {
MOContextScope scope =
new DefaultMOContextScope(context, key, true, key, true);
MOQuery query = new DefaultMOQuery(scope);
SortedMap> reducedScope = registry.tailMap(query);
for (Entry> entry : reducedScope.entrySet()) {
MOScope compareScope = entry.getKey();
if (!MOScopeComparator.isQueryContextMatching(query, compareScope)) {
continue;
}
ManagedObject> mo = entry.getValue();
if (((mo instanceof RegisteredManagedObject) && ((RegisteredManagedObject) mo).getID().equals(key)) ||
(!(mo instanceof RegisteredManagedObject) && key.equals(mo.getScope().getLowerBound()))) {
if (fireLookupEvents) {
MOServerLookupEvent event =
new MOServerLookupEvent(this, mo, query, MOServerLookupEvent.IntendedUse.undefined);
fireQueryEvent(mo, event);
boolean locked = false;
if (mo instanceof UpdatableManagedObject) {
locked = lock(this, mo);
if (locked) {
checkForUpdate((UpdatableManagedObject) mo, query);
}
}
fireLookupEvent(mo, event);
if (locked) {
unlock(this, mo);
}
}
return mo;
}
}
return null;
}
/**
* Returns the {@code ManagedObject} with the specified {@code OID}
* as ID returned by {@link RegisteredManagedObject#getID()} or the lower bound
* (regardless whether the ManagedObject's scope includes it or not)
* when registered in the supplied context.
*
* Note: The query used to lookup the managed object will indicate an intended
* read-only access for the {@link MOServerLookupEvent}s fired on behalf of
* this method.
*
* @param key
* the OID identifying the key (lower bound) of the
* {@code ManagedObject}.
* @param context
* the optional context to look in. A {@code null} value searches
* in all contexts.
*
* @return the {@code ManagedObject} instance or {@code null} if such
* an instance does not exists.
* @since 1.1
*/
public ManagedObject> getManagedObject(OID key, OctetString context) {
return getManagedObject(key, context, true);
}
/**
* Returns the value of a particular MIB object instance using the
* {@link ManagedObjectValueAccess} interface. If a {@link ManagedObject}
* does not support this interface, its value cannot be returned and
* {@code null} will be returned instead.
* Note: This method does not perform any locking based on the {@link MOLockStrategy}.
*
* @param server
* the {@code MOServer} where to lookup the value.
* @param context
* the optional context to look in. A {@code null} value searches
* in all contexts.
* @param key
* the OID identifying the variable instance to return.
*
* @return the {@code Variable} associated with {@code OID} and
* {@code context} in {@code server} or {@code null} if
* no such variable exists.
* @since 1.4
*/
public static Variable getValue(MOServer server, OctetString context, OID key) {
MOContextScope scope =
new DefaultMOContextScope(context, key, true, key, true);
MOServerLookupEvent lookupEvent =
new MOServerLookupEvent(server, null, new DefaultMOQuery(scope),
MOServerLookupEvent.IntendedUse.get, true);
ManagedObject> mo = server.lookup(lookupEvent.getQuery(), null, lookupEvent);
if (mo instanceof ManagedObjectValueAccess) {
Variable variable = ((ManagedObjectValueAccess) mo).getValue(key);
lookupEvent.completedUse(variable);
return variable;
}
return null;
}
/**
* Sets the value of a particular MIB object instance using the
* {@link ManagedObjectValueAccess} interface. If a {@link ManagedObject}
* does not support this interface, its value cannot be set and
* {@code false} will be returned.
* Note: This method does not perform any locking based on the {@link MOLockStrategy}.
*
* @param server
* the {@code MOServer} where to lookup the value.
* @param context
* the optional context to look in. A {@code null} value searches
* in all contexts.
* @param newValueAndKey
* the OID identifying the variable instance to set and its new value.
*
* @return the {@code true} if the value has been set successfully,
* {@code false} otherwise.
* @since 1.4
*/
public static boolean setValue(MOServer server, OctetString context,
VariableBinding newValueAndKey) {
OID key = newValueAndKey.getOid();
MOContextScope scope =
new DefaultMOContextScope(context, key, true, key, true);
MOServerLookupEvent lookupEvent = new MOServerLookupEvent(server, null,
new DefaultMOQuery(scope), MOServerLookupEvent.IntendedUse.update, true);
ManagedObject> mo = server.lookup(lookupEvent.getQuery(), null, lookupEvent);
if (mo instanceof ManagedObjectValueAccess) {
boolean variableSet = ((ManagedObjectValueAccess) mo).setValue(newValueAndKey);
if (variableSet) {
lookupEvent.completedUse(newValueAndKey);
} else {
lookupEvent.completedUse(null);
}
return variableSet;
}
lookupEvent.completedUse(null);
return false;
}
protected void fireLookupEvent(ManagedObject> mo, MOServerLookupEvent event) {
if (lookupListener != null) {
List l = lookupListener.get(mo);
if (l != null) {
callLookupListeners(event, l);
}
l = lookupListener.get(null);
if (l != null) {
callLookupListeners(event, l);
}
}
}
private void callLookupListeners(MOServerLookupEvent event, List l) {
// avoid concurrent modification exception
ArrayList listCopy = new ArrayList<>(l);
for (MOServerLookupListener item : listCopy) {
item.lookupEvent(event);
}
}
protected void fireQueryEvent(ManagedObject> mo, MOServerLookupEvent event) {
if (lookupListener != null) {
List l = lookupListener.get(mo);
if (l != null) {
// avoid concurrent modification exception
l = new ArrayList<>(l);
for (MOServerLookupListener item : l) {
item.queryEvent(event);
}
}
}
}
public OctetString[] getContexts() {
return contexts.toArray(new OctetString[0]);
}
public boolean isContextSupported(OctetString context) {
if ((context == null) || (context.length() == 0)) {
return true;
}
return contexts.contains(context);
}
public SortedMap> getRegistry() {
return registry;
}
/**
* Gets the update strategy for {@link UpdatableManagedObject}s. If the
* strategy is {@code null} no updates will be performed on behalf
* of calls to {@link #lookup}.
*
* @return the current UpdateStrategy instance or {@code null} if no
* strategy is active.
* @see #lookup
* @since 1.2
*/
public UpdateStrategy getUpdateStrategy() {
return updateStrategy;
}
/**
* Sets the update strategy for {@link UpdatableManagedObject}s. If the
* strategy is {@code null} no updates will be performed on behalf
* of calls to {@link #lookup(MOQuery)}.
*
* @param updateStrategy
* the new UpdateStrategy instance or {@code null} if no
* updates should be performed.
*
* @see #lookup(MOQuery)
* @since 1.2
*/
public void setUpdateStrategy(UpdateStrategy updateStrategy) {
this.updateStrategy = updateStrategy;
}
@Override
public void register(ManagedObject> mo, OctetString context) throws DuplicateRegistrationException {
if ((context == null) || (context.length() == 0)) {
MOContextScope contextScope =
new DefaultMOContextScope(null, mo.getScope());
MOServerLookupEvent lookupEvent =
new MOServerLookupEvent(this, mo, new DefaultMOQuery(contextScope),
MOServerLookupEvent.IntendedUse.register);
ManagedObject> other = lookup(new DefaultMOQuery(contextScope), null, lookupEvent);
if (other != null) {
throw new DuplicateRegistrationException(contextScope, other.getScope());
}
registry.put(mo.getScope(), mo);
if (logger.isInfoEnabled()) {
logger.info("Registered MO [" + mo.getClass().getSimpleName() + "] in default context with scope " +
mo.getScope());
}
} else {
DefaultMOContextScope contextScope =
new DefaultMOContextScope(context, mo.getScope());
MOServerLookupEvent lookupEvent =
new MOServerLookupEvent(this, mo, new DefaultMOQuery(contextScope),
MOServerLookupEvent.IntendedUse.register);
ManagedObject> other =
lookup(lookupEvent.getQuery(), true, null, lookupEvent, ManagedObject.class);
if (other != null) {
throw new DuplicateRegistrationException(contextScope, other.getScope());
}
registry.put(contextScope, mo);
if (logger.isInfoEnabled()) {
logger.info("Registered MO [" + mo.getClass().getSimpleName() + "] in context " + context + " with scope " +
contextScope);
}
}
}
@Override
public ManagedObject> unregister(ManagedObject> mo, OctetString context) {
MOScope key;
if ((context == null) || (context.length() == 0)) {
key = mo.getScope();
} else {
key = new DefaultMOContextScope(context, mo.getScope());
}
ManagedObject> r = registry.remove(key);
if (r == null) {
// OK, may be the upper bound of the scope has been adjusted so we need to
// check that by iterating
SortedMap> tailMap = registry.tailMap(key);
for (Iterator>> it = tailMap.entrySet().iterator(); it.hasNext(); ) {
Entry> entry = it.next();
MOScope entryKey = entry.getKey();
if ((entry.getValue().equals(mo)) &&
(context == null || ((entryKey instanceof MOContextScope) &&
(context.equals(((MOContextScope) entryKey).getContext()))))) {
r = entry.getValue();
it.remove();
break;
}
}
}
if (r != null) {
MOServerLookupEvent event =
new MOServerLookupEvent(this, r, new DefaultMOQuery(key instanceof MOContextScope ?
(MOContextScope) key : new DefaultMOContextScope(null, key)),
MOServerLookupEvent.IntendedUse.unregister);
fireLookupEvent(r, event);
}
if (logger.isInfoEnabled()) {
if (r != null) {
logger.info("Removed registration " + r + " for " + mo.getScope() +
" in context '" + context + "' successfully");
} else {
logger.warn("Removing registration failed for " + mo.getScope() +
" in context '" + context + "'");
}
}
return r;
}
public void addContext(OctetString context) {
contexts.add(context);
fireContextChanged(new ContextEvent(this, ContextEvent.CONTEXT_ADDED, context));
}
public void removeContext(OctetString context) {
contexts.remove(context);
fireContextChanged(new ContextEvent(this, ContextEvent.CONTEXT_REMOVED, context));
}
public synchronized boolean lock(Object owner, ManagedObject> managedObject) {
return lock(owner, managedObject, 0);
}
public synchronized boolean lock(Object owner, ManagedObject> managedObject,
long timeoutMillis) {
Lock lock;
long start = System.nanoTime();
do {
lock = lockList.get(managedObject);
if ((lock == null) || ((lock.getOwner() != owner) && (lock.getCount() <= 0))) {
lockList.put(managedObject, new Lock(owner));
if (logger.isDebugEnabled()) {
logger.debug("Acquired lock on " + managedObject + " for " + owner);
}
return true;
} else if (lock.getOwner() != owner) {
try {
while ((lock.getCount() > 0) &&
((System.nanoTime() - start) / SnmpConstants.MILLISECOND_TO_NANOSECOND < timeoutMillis)) {
if (logger.isDebugEnabled()) {
logger.debug("Waiting for lock on " + managedObject);
}
if (timeoutMillis <= 0) {
wait();
} else {
wait(Math.max(timeoutMillis -
(System.nanoTime() - start) / SnmpConstants.MILLISECOND_TO_NANOSECOND, 1));
}
}
} catch (InterruptedException ex) {
logger.warn("Waiting for lock on " + managedObject +
" has been interrupted!");
break;
}
} else {
lock.add();
if (logger.isDebugEnabled()) {
logger.debug("Added lock on " + managedObject + " for " + owner);
}
return true;
}
}
while (((timeoutMillis <= 0) ||
((System.nanoTime() - start) / SnmpConstants.MILLISECOND_TO_NANOSECOND < timeoutMillis)));
return false;
}
public synchronized boolean unlock(Object owner, ManagedObject> managedObject) {
if (managedObject != null) {
Lock lock = lockList.get(managedObject);
if (lock != null) {
if (lock.getOwner() != owner) {
if (logger.isDebugEnabled()) {
logger.debug("Object '" + owner + "' is not owner of lock: " + lock);
}
} else if (lock.remove()) {
lockList.remove(managedObject);
if (logger.isDebugEnabled()) {
logger.debug("Removed lock on " + managedObject + " by " + owner);
}
notify();
return true;
}
}
}
return false;
}
public Iterator>> iterator() {
SortedMap> m = getRegistry();
synchronized (m) {
SortedMap> r = new TreeMap<>(new MOScopeComparator());
r.putAll(m);
return r.entrySet().iterator();
}
}
public synchronized void addLookupListener(MOServerLookupListener listener,
ManagedObject> mo) {
if (lookupListener == null) {
lookupListener = Collections.synchronizedMap(new HashMap<>());
}
List l =
lookupListener.computeIfAbsent(mo, k -> Collections.synchronizedList(new LinkedList<>()));
l.add(listener);
}
public synchronized boolean removeLookupListener(MOServerLookupListener listener, ManagedObject> mo) {
if (lookupListener != null) {
List l = lookupListener.get(mo);
if (l != null) {
return l.remove(listener);
}
}
return false;
}
public synchronized void addContextListener(ContextListener l) {
if (contextListeners == null) {
contextListeners = new ArrayList<>(2);
}
contextListeners.add(l);
}
public synchronized void removeContextListener(ContextListener l) {
if (contextListeners != null) {
contextListeners.remove(l);
}
}
protected void fireContextChanged(ContextEvent event) {
if (contextListeners != null) {
List listeners = contextListeners;
for (ContextListener listener : listeners) {
listener.contextChanged(event);
}
}
}
public String toString() {
StringBuilder buf = new StringBuilder(getClass().getName());
buf.append("[contexts=");
buf.append(contexts);
buf.append("[keys={");
for (Iterator it = registry.keySet().iterator(); it.hasNext(); ) {
MOScope scope = it.next();
buf.append(scope.getLowerBound());
if (scope.isLowerIncluded()) {
buf.append("+");
}
buf.append("-");
buf.append(scope.getUpperBound());
if (scope.isUpperIncluded()) {
buf.append("+");
}
if (scope instanceof MOContextScope) {
buf.append("(").append(((MOContextScope) scope).getContext()).append(")");
}
if (it.hasNext()) {
buf.append(",");
}
}
buf.append("}");
buf.append(",registry=").append(registry);
buf.append(",lockList=").append(lockList);
buf.append(",lookupListener=").append(lookupListener);
buf.append("]");
return buf.toString();
}
@Override
public OctetString[] getRegisteredContexts(ManagedObject> managedObject) {
Set contextSet = new HashSet<>();
SortedMap> scope = registry.tailMap(new DefaultMOContextScope(null,
managedObject.getScope()));
for (Entry> entry : scope.entrySet()) {
MOScope key = entry.getKey();
ManagedObject> o = entry.getValue();
if (managedObject.equals(o)) {
if (key instanceof MOContextScope) {
contextSet.add(((MOContextScope) key).getContext());
} else {
contextSet.add(null);
}
}
}
return contextSet.toArray(new OctetString[0]);
}
/**
* Register a single {@link MOTableRowListener} with all tables in the
* specified {@link MOServer}. This overall registration can be used,
* for example, to apply table size limits to all tables in an agent.
* See {@link org.snmp4j.agent.mo.util.MOTableSizeLimit} for details.
*
* Note: The server must not change its registration content while this
* method is being called, otherwise a
* {@link java.util.ConcurrentModificationException} might be thrown.
*
* @param server
* a {@code MOServer} instance.
* @param listener
* the {@code MOTableRowListener} instance to register.
* @param
* the {@link MOTableRow} type supported by the table row listener to register.
* @param
* the {@link MOTable} type supported by the table row listener to register.
*
* @since 1.4
*/
@SuppressWarnings({"unchecked","rawtypes"})
public static >>
void registerTableRowListener(MOServer server, MOTableRowListener listener) {
for (Iterator>> it = server.iterator(); it.hasNext(); ) {
ManagedObject> mo = it.next().getValue();
if (mo instanceof MOTable) {
((T) mo).addMOTableRowListener(listener);
}
}
}
/**
* Unregister a single {@link MOTableRowListener} with all tables in the
* specified {@link MOServer}. This overall unregistration can be used,
* for example, to remove table size limits from all tables in an agent.
* See {@link org.snmp4j.agent.mo.util.MOTableSizeLimit} for details.
*
* Note: The server must not change its registration content while this
* method is being called, otherwise a
* {@link java.util.ConcurrentModificationException} might be thrown.
*
* @param server
* a {@code MOServer} instance.
* @param listener
* the {@code MOTableRowListener} instance to unregister.
* @param
* the {@link MOTableRow} type supported by the table row listener to register.
* @param
* the {@link MOTable} type supported by the table row listener to register.
*
* @since 1.4
*/
@SuppressWarnings({"unchecked","rawtypes"})
public static >>
void unregisterTableRowListener(MOServer server, MOTableRowListener listener) {
for (Iterator>> it = server.iterator(); it.hasNext(); ) {
ManagedObject> mo = it.next().getValue();
if (mo instanceof MOTable) {
((T) mo).removeMOTableRowListener(listener);
}
}
}
/**
* Register a single {@link MOChangeListener} with all objects matching the given filter in the
* specified {@link MOServer}. This overall registration can be used,
* for example, to listen for object changes.
*
* Note: The server must not change its registration content while this
* method is being called, otherwise a
* {@link java.util.ConcurrentModificationException} might be thrown.
*
* @param server
* a {@code MOServer} instance.
* @param listener
* the {@code MOTableRowListener} instance to register.
* @param moFilter
* an optional filter to select objects which should register the provided listener.
*
* @since 3.0
*/
public static void registerChangeListener(MOServer server, MOChangeListener listener, MOFilter moFilter) {
for (Iterator>> it = server.iterator(); it.hasNext(); ) {
ManagedObject> mo = it.next().getValue();
if (mo instanceof ChangeableManagedObject && (moFilter == null || moFilter.passesFilter(mo))) {
((ChangeableManagedObject) mo).addMOChangeListener(listener);
}
}
}
/**
* Unregister a single {@link MOChangeListener} from all objects matching the given filter in the
* specified {@link MOServer}.
*
* Note: The server must not change its registration content while this
* method is being called, otherwise a
* {@link java.util.ConcurrentModificationException} might be thrown.
*
* @param server
* a {@code MOServer} instance.
* @param listener
* the {@code MOTableRowListener} instance to unregister.
* @param moFilter
* an optional filter to select objects which should no longer register the provided listener.
*
* @since 3.0
*/
public static void unregisterChangeListener(MOServer server, MOChangeListener listener, MOFilter moFilter) {
for (Iterator>> it = server.iterator(); it.hasNext(); ) {
ManagedObject> mo = it.next().getValue();
if (mo instanceof ChangeableManagedObject && (moFilter == null || moFilter.passesFilter(mo))) {
((ChangeableManagedObject) mo).removeMOChangeListener(listener);
}
}
}
static class Lock {
private Object owner;
private long creationTime;
private int count = 0;
private Lock() {
this.creationTime = System.currentTimeMillis();
this.count = 0;
}
Lock(Object owner) {
this();
this.owner = owner;
this.count = 1;
}
public long getCreationTime() {
return creationTime;
}
public int getCount() {
return count;
}
public synchronized void add() {
count++;
}
public synchronized boolean remove() {
if ((--count) <= 0) {
if (count != 0) {
count = 0;
}
return true;
}
return false;
}
public Object getOwner() {
return owner;
}
@Override
public String toString() {
return "Lock[" +
"owner=" + owner +
", creationTime=" + creationTime +
", count=" + count +
']';
}
}
}