/* * JBoss, Home of Professional Open Source. * Copyright 2010, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.naming; import java.security.PrivilegedAction; import java.util.concurrent.ThreadFactory; import org.jboss.as.naming.util.FastCopyHashMap; import javax.naming.Binding; import javax.naming.Name; import javax.naming.event.EventContext; import javax.naming.event.NamespaceChangeListener; import javax.naming.event.NamingEvent; import javax.naming.event.NamingListener; import javax.naming.event.ObjectChangeListener; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import org.jboss.threads.JBossThreadFactory; import static java.security.AccessController.doPrivileged; /** * Coordinator responsible for passing @(code NamingEvent} instances to registered @{code NamingListener} instances. Two * maps are used to managed a mapping between a listener and its configuration as well as a mapping from target name to a list * of listener configurations. These maps are updated atomically on listener add and remove. * * @author John E. Bailey */ public class NamingEventCoordinator { private volatile Map<TargetScope, List<ListenerHolder>> holdersByTarget = Collections.emptyMap(); private volatile Map<NamingListener, ListenerHolder> holdersByListener = Collections.emptyMap(); private final ThreadFactory threadFactory = doPrivileged(new PrivilegedAction<JBossThreadFactory>() { public JBossThreadFactory run() { return new JBossThreadFactory(new ThreadGroup("NamingEventCoordinator-threads"), Boolean.FALSE, null, "%G - %t", null, null); } }); private final Executor executor = Executors.newSingleThreadExecutor(threadFactory); static final Integer[] DEFAULT_SCOPES = {EventContext.OBJECT_SCOPE, EventContext.ONELEVEL_SCOPE, EventContext.SUBTREE_SCOPE}; /** * Add a listener to the coordinator with a given target name and event scope. This information is used when an * event is fired to determine whether or not to fire this listener. * * @param target The target name to lister * @param scope The event scope * @param namingListener The listener */ synchronized void addListener(final String target, final int scope, final NamingListener namingListener) { final TargetScope targetScope = new TargetScope(target, scope); // Do we have a holder for this listener ListenerHolder holder = holdersByListener.get(namingListener); if (holder == null) { holder = new ListenerHolder(namingListener, targetScope); final Map<NamingListener, ListenerHolder> byListenerCopy = new FastCopyHashMap<NamingListener, ListenerHolder>(holdersByListener); byListenerCopy.put(namingListener, holder); holdersByListener = byListenerCopy; } else { holder.addTarget(targetScope); } List<ListenerHolder> holdersForTarget = holdersByTarget.get(targetScope); if (holdersForTarget == null) { holdersForTarget = new CopyOnWriteArrayList<ListenerHolder>(); final Map<TargetScope, List<ListenerHolder>> byTargetCopy = new FastCopyHashMap<TargetScope, List<ListenerHolder>>(holdersByTarget); byTargetCopy.put(targetScope, holdersForTarget); holdersByTarget = byTargetCopy; } holdersForTarget.add(holder); } /** * Remove a listener. Will remove it from all target mappings. Once this method returns, the listener will no longer * receive any events. * * @param namingListener The listener */ synchronized void removeListener(final NamingListener namingListener) { // Do we have a holder for this listener final ListenerHolder holder = holdersByListener.get(namingListener); if (holder == null) { return; } final Map<NamingListener, ListenerHolder> byListenerCopy = new FastCopyHashMap<NamingListener, ListenerHolder>(holdersByListener); byListenerCopy.remove(namingListener); holdersByListener = byListenerCopy; final Map<TargetScope, List<ListenerHolder>> byTargetCopy = new FastCopyHashMap<TargetScope, List<ListenerHolder>>(holdersByTarget); for (TargetScope targetScope : holder.targets) { final List<ListenerHolder> holders = holdersByTarget.get(targetScope); holders.remove(holder); if (holders.isEmpty()) { byTargetCopy.remove(targetScope); } } holdersByTarget = byTargetCopy; } /** * Fire a naming event. An event will be created with the provided information and sent to each listener that matches * the target and scope information. * * @param context The event context generating the event. * @param name The target name the event represents * @param existingBinding The existing binding at the provided name * @param newBinding The new binding at the provided name * @param type The event type * @param changeInfo The change info for the event * @param scopes The scopes this event should be fired against */ void fireEvent(final EventContext context, final Name name, final Binding existingBinding, final Binding newBinding, int type, final String changeInfo, final Integer... scopes) { final String target = name.toString(); final Set<Integer> scopeSet = new HashSet<Integer>(Arrays.asList(scopes)); final NamingEvent event = new NamingEvent(context, type, newBinding, existingBinding, changeInfo); final Set<ListenerHolder> holdersToFire = new HashSet<ListenerHolder>(); // Check for OBJECT_SCOPE based listeners if (scopeSet.contains(EventContext.OBJECT_SCOPE)) { final TargetScope targetScope = new TargetScope(target, EventContext.OBJECT_SCOPE); final List<ListenerHolder> holders = holdersByTarget.get(targetScope); if (holders != null) { for (ListenerHolder holder : holders) { holdersToFire.add(holder); } } } // Check for ONELEVEL_SCOPE based listeners if (scopeSet.contains(EventContext.ONELEVEL_SCOPE) && !name.isEmpty()) { final TargetScope targetScope = new TargetScope(name.getPrefix(name.size() - 1).toString(), EventContext.ONELEVEL_SCOPE); final List<ListenerHolder> holders = holdersByTarget.get(targetScope); if (holders != null) { for (ListenerHolder holder : holders) { holdersToFire.add(holder); } } } // Check for SUBTREE_SCOPE based listeners if (scopeSet.contains(EventContext.SUBTREE_SCOPE) && !name.isEmpty()) { for (int i = 1; i < name.size(); i++) { final Name parentName = name.getPrefix(i); final TargetScope targetScope = new TargetScope(parentName.toString(), EventContext.SUBTREE_SCOPE); final List<ListenerHolder> holders = holdersByTarget.get(targetScope); if (holders != null) { for (ListenerHolder holder : holders) { holdersToFire.add(holder); } } } } executor.execute(new FireEventTask(holdersToFire, event)); } private class FireEventTask implements Runnable { private final Set<ListenerHolder> listenerHolders; private final NamingEvent event; private FireEventTask(Set<ListenerHolder> listenerHolders, NamingEvent event) { this.listenerHolders = listenerHolders; this.event = event; } @Override public void run() { for (ListenerHolder holder : listenerHolders) { final NamingListener listener = holder.listener; switch (event.getType()) { case NamingEvent.OBJECT_ADDED: if (listener instanceof NamespaceChangeListener) ((NamespaceChangeListener) listener).objectAdded(event); break; case NamingEvent.OBJECT_REMOVED: if (listener instanceof NamespaceChangeListener) ((NamespaceChangeListener) listener).objectRemoved(event); break; case NamingEvent.OBJECT_RENAMED: if (listener instanceof NamespaceChangeListener) ((NamespaceChangeListener) listener).objectRenamed(event); break; case NamingEvent.OBJECT_CHANGED: if (listener instanceof ObjectChangeListener) ((ObjectChangeListener) listener).objectChanged(event); break; } } } } private class ListenerHolder { private volatile Set<TargetScope> targets = new HashSet<TargetScope>(); private final NamingListener listener; private ListenerHolder(final NamingListener listener, final TargetScope initialTarget) { this.listener = listener; addTarget(initialTarget); } private synchronized void addTarget(final TargetScope targetScope) { targets.add(targetScope); } } private class TargetScope { private final String target; private final int scope; private TargetScope(String target, int scope) { this.target = target; this.scope = scope; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TargetScope that = (TargetScope) o; if (scope != that.scope) return false; if (target != null ? !target.equals(that.target) : that.target != null) return false; return true; } @Override public int hashCode() { int result = target != null ? target.hashCode() : 0; result = 31 * result + scope; return result; } } }