/** * Copyright 2010 JBoss Inc * * 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.drools.io.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingQueue; import org.drools.ChangeSet; import org.drools.SystemEventListener; import org.drools.SystemEventListenerFactory; import org.drools.event.io.ResourceChangeListener; import org.drools.io.Resource; import org.drools.io.ResourceChangeMonitor; import org.drools.io.ResourceChangeNotifier; public class ResourceChangeNotifierImpl implements ResourceChangeNotifier { private Map<Resource, Set<ResourceChangeListener>> subscriptions; private List<ResourceChangeMonitor> monitors; private SystemEventListener listener; private LinkedBlockingQueue<ChangeSet> queue; public ResourceChangeNotifierImpl() { this.listener = SystemEventListenerFactory.getSystemEventListener(); this.subscriptions = new HashMap<Resource, Set<ResourceChangeListener>>(); this.monitors = new CopyOnWriteArrayList<ResourceChangeMonitor>(); this.queue = new LinkedBlockingQueue<ChangeSet>(); this.listener.info( "ResourceChangeNotification created" ); } public void setSystemEventListener(SystemEventListener listener) { this.listener = listener; } public void addResourceChangeMonitor(ResourceChangeMonitor monitor) { if ( !this.monitors.contains( monitor ) ) { this.listener.debug( "ResourceChangeNotification monitor added monitor=" + monitor ); this.monitors.add( monitor ); } } public void removeResourceChangeMonitor(ResourceChangeMonitor monitor) { this.listener.debug( "ResourceChangeNotification monitor removed monitor=" + monitor ); this.monitors.remove( monitor ); } public Collection<ResourceChangeMonitor> getResourceChangeMonitors() { return Collections.unmodifiableCollection( this.monitors ); } public void subscribeResourceChangeListener(ResourceChangeListener listener, Resource resource) { this.listener.debug( "ResourceChangeNotification subscribing listener=" + listener + " to resource=" + resource ); synchronized ( this.subscriptions ) { Set<ResourceChangeListener> listeners = this.subscriptions.get( resource ); if ( listeners == null ) { listeners = new HashSet<ResourceChangeListener>(); this.subscriptions.put( resource, listeners ); for ( ResourceChangeMonitor monitor : this.monitors ) { monitor.subscribeNotifier( this, resource ); } } listeners.add( listener ); } } public void unsubscribeResourceChangeListener(ResourceChangeListener listener, Resource resource) { this.listener.debug( "ResourceChangeNotification unsubscribing listener=" + listener + " to resource=" + resource ); synchronized ( this.subscriptions ) { Set<ResourceChangeListener> listeners = this.subscriptions.get( resource ); if ( listeners == null ) { return; } listeners.remove( listener ); if ( listeners.isEmpty() ) { this.subscriptions.remove( resource ); for ( ResourceChangeMonitor monitor : this.monitors ) { monitor.unsubscribeNotifier( this, resource ); } } } } public void subscribeChildResource(Resource directory, Resource child) { this.listener.debug( "ResourceChangeNotification subscribing directory=" + directory + " content resource=" + child ); for ( ResourceChangeListener listener : this.subscriptions.get( directory ) ) { subscribeResourceChangeListener( listener, child ); } } public void publishChangeSet(ChangeSet changeSet) { try { this.listener.debug( "ResourceChangeNotification received ChangeSet notification" ); this.queue.put( changeSet ); } catch ( InterruptedException e ) { this.listener.exception( new RuntimeException( "ResourceChangeNotification Exception while adding to notification queue", e ) ); } } public void processChangeSet(ChangeSet changeSet) { // this provides the complete published change set for this notifier. // however different listeners might be listening to different resources, so provide // listener change specified change sets. Map<ResourceChangeListener, ChangeSetImpl> localChangeSets = new HashMap<ResourceChangeListener, ChangeSetImpl>(); this.listener.debug( "ResourceChangeNotification processing ChangeSet" ); for ( Resource resource : changeSet.getResourcesAdded() ) { Set<ResourceChangeListener> listeners = this.subscriptions.get( resource ); for ( ResourceChangeListener listener : listeners ) { ChangeSetImpl localChangeSet = localChangeSets.get( listener ); if ( localChangeSet == null ) { // lazy initialise changeSet localChangeSet = new ChangeSetImpl(); localChangeSets.put( listener, localChangeSet ); } if ( localChangeSet.getResourcesAdded().isEmpty() ) { localChangeSet.setResourcesAdded( new ArrayList<Resource>() ); } localChangeSet.getResourcesAdded().add( resource ); this.listener.debug( "ResourceChangeNotification ChangeSet added resource=" + resource + " for listener=" + listener ); } } for ( Resource resource : changeSet.getResourcesRemoved() ) { Set<ResourceChangeListener> listeners = this.subscriptions.remove( resource ); for ( ResourceChangeListener listener : listeners ) { ChangeSetImpl localChangeSet = localChangeSets.get( listener ); if ( localChangeSet == null ) { // lazy initialise changeSet localChangeSet = new ChangeSetImpl(); localChangeSets.put( listener, localChangeSet ); } if ( localChangeSet.getResourcesRemoved().isEmpty() ) { localChangeSet.setResourcesRemoved( new ArrayList<Resource>() ); } localChangeSet.getResourcesRemoved().add( resource ); this.listener.debug( "ResourceChangeNotification ChangeSet removed resource=" + resource + " for listener=" + listener ); } } for ( Resource resource : changeSet.getResourcesModified() ) { Set<ResourceChangeListener> listeners = this.subscriptions.get( resource ); for ( ResourceChangeListener listener : listeners ) { ChangeSetImpl localChangeSet = localChangeSets.get( listener ); if ( localChangeSet == null ) { // lazy initialise changeSet localChangeSet = new ChangeSetImpl(); localChangeSets.put( listener, localChangeSet ); } if ( localChangeSet.getResourcesModified().isEmpty() ) { localChangeSet.setResourcesModified( new ArrayList<Resource>() ); } localChangeSet.getResourcesModified().add( resource ); this.listener.debug( "ResourceChangeNotification ChangeSet modified resource=" + resource + " for listener=" + listener ); } } for ( Entry<ResourceChangeListener, ChangeSetImpl> entry : localChangeSets.entrySet() ) { ResourceChangeListener listener = entry.getKey(); ChangeSetImpl localChangeSet = entry.getValue(); listener.resourcesChanged( localChangeSet ); } // ResourceModifiedEvent event = new ResourceModifiedEventImpl( resource, // resource.getLastModified() ); // Set<ResourceChangeListener> listeners = this.subscriptions.get( resource ); // // if ( listeners != null ) { // for ( ResourceChangeListener listener : listeners ) { // listener.resourceModified( event ); // } // } } public void start() { this.processChangeSet = new ProcessChangeSet( this.queue, this, this.listener ); this.thread = new Thread( this.processChangeSet ); this.thread.start(); } public void stop() { this.processChangeSet.stop(); this.thread.interrupt(); this.processChangeSet = null; } public void reset() { this.subscriptions.clear(); this.monitors.clear(); } private Thread thread; private ProcessChangeSet processChangeSet; public static class ProcessChangeSet implements Runnable { private volatile boolean notify; private LinkedBlockingQueue<ChangeSet> queue; private ResourceChangeNotifierImpl notifier; private SystemEventListener listener; ProcessChangeSet(LinkedBlockingQueue<ChangeSet> queue, ResourceChangeNotifierImpl notifier, SystemEventListener listener) { this.queue = queue; this.notifier = notifier; this.listener = listener; this.notify = true; } public void stop() { this.notify = false; } public boolean isRunning() { return this.notify; } public void run() { if ( this.notify ) { this.listener.info( "ResourceChangeNotification has started listening for ChangeSet publications" ); } while ( this.notify ) { Exception exception = null; try { this.listener.debug( "ResourceChangeNotification thread is waiting for queue update" ); this.notifier.processChangeSet( this.queue.take() ); } catch ( InterruptedException e ) { exception = e; } Thread.yield(); if ( this.notify && exception != null ) { this.listener.exception( new RuntimeException( "ResourceChangeNotification ChangeSet publication thread was interrupted, but shutdown was not scheduled", exception ) ); } } this.listener.info( "ResourceChangeNotification has stopped listening for ChangeSet publications" ); } } }