/*************************************************************************
* Copyright 2009-2012 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.event;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.eucalyptus.event.Event.Periodic;
import com.eucalyptus.system.Ats;
import com.google.common.base.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.log4j.Logger;
import com.eucalyptus.component.ComponentId;
import com.eucalyptus.component.ServiceConfigurations;
import com.eucalyptus.empyrean.Empyrean;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Classes;
import com.eucalyptus.util.Exceptions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
public class ListenerRegistry {
private static Logger LOG = Logger.getLogger( ListenerRegistry.class );
private static ListenerRegistry singleton = new ListenerRegistry();
private final Map<Class, ReentrantListenerRegistry> registryMap;
private final ReentrantListenerRegistry<Class<? extends Event>> eventMap;
public static ListenerRegistry getInstance( ) {
return singleton;
}
@SuppressWarnings( "unchecked" )
public ListenerRegistry( ) {
super( );
this.registryMap = Maps.newHashMap( );
this.eventMap = new ReentrantListenerRegistry<Class<? extends Event>>( );
this.registryMap.put( ComponentId.class, new ReentrantListenerRegistry<ComponentId>( ) );
this.registryMap.put( String.class, new ReentrantListenerRegistry<String>( ) );
}
@SuppressWarnings( "unchecked" )
public void register( Object type, final EventListener listener ) {
final List<Class> lookupTypes = Classes.genericsToClasses( listener );
lookupTypes.remove( Event.class );
/**
* GRZE: event type is not specified by the generic type of listeners EventListener decl or is
* <Event>
**/
boolean illegal = ( type == null && lookupTypes.isEmpty( ) );
/** GRZE: explicit event type does conform to generic type **/
for ( Class<?> c : lookupTypes ) {
if ( type != null && c.isAssignableFrom( ( type instanceof Class )
? ( Class ) type
: type.getClass( ) ) ) {
illegal = false;
break;
}
}
if ( illegal ) {
throw Exceptions.error( new IllegalArgumentException( "Failed to register listener " + listener.getClass( ).getCanonicalName( )
+ " because the declared generic type " + lookupTypes
+ " is not assignable from the provided event type: " + ( type != null
? type.getClass( ).getCanonicalName( )
: "null" ) ) );
} else {
Class key = ( type == null
? lookupTypes.get( 0 )
: ( type instanceof Class
? ( Class ) type
: type.getClass( ) ) );
if ( Event.class.isAssignableFrom( key ) ) {
this.eventMap.register( key, listener );
} else {
if ( !this.registryMap.containsKey( key ) ) {
this.registryMap.put( key, new ReentrantListenerRegistry( ) );
}
this.registryMap.get( key ).register( type, listener );
}
}
}
@SuppressWarnings( "unchecked" )
public void deregister( final Object type, final EventListener listener ) {
final List<Class> lookupTypes = Classes.genericsToClasses( listener );
lookupTypes.remove( Event.class );
/**
* GRZE: event type is not specified by the generic type of listeners EventListener decl or is
* <Event>
**/
boolean illegal = ( type == null && lookupTypes.isEmpty( ) );
for ( Class<?> c : lookupTypes ) {
if ( type != null && c.isAssignableFrom( ( type instanceof Class )
? ( Class ) type
: type.getClass( ) ) ) {
illegal = false;
break;
}
}
if ( illegal ) {
throw Exceptions.error( new IllegalArgumentException( "Failed to register listener " + listener.getClass( ).getCanonicalName( )
+ " because the declared generic type " + lookupTypes
+ " is not assignable from the provided event type: " + ( type != null
? type.getClass( ).getCanonicalName( )
: "null" ) ) );
} else {
if ( ( type instanceof Class ) && Event.class.isAssignableFrom( ( Class ) type ) ) {
this.eventMap.deregister( ( Class ) type, listener );
} else {
if ( !this.registryMap.containsKey( type.getClass( ) ) ) {
this.registryMap.put( type.getClass( ), new ReentrantListenerRegistry( ) );
}
this.registryMap.get( type.getClass( ) ).deregister( type, listener );
}
}
}
@SuppressWarnings( "unchecked" )
public void destroy( final Object type ) {
if ( ( type instanceof Class ) && Event.class.isAssignableFrom( ( Class ) type ) ) {
this.eventMap.destroy( ( Class ) type );
} else {
if ( !this.registryMap.containsKey( type.getClass( ) ) ) {
this.registryMap.put( type.getClass( ), new ReentrantListenerRegistry( ) );
}
this.registryMap.get( type.getClass( ) ).destroy( type );
}
}
public void fireEvent( final Event e ) throws EventFailedException {
this.eventMap.fireEvent( e.getClass( ), e );
}
public void fireThrowableEvent( final Event e) throws EventFailedException {
this.eventMap.fireThrowableEvent(e.getClass(), e);
}
@SuppressWarnings( "unchecked" )
public void fireEvent( final Object type, final Event e ) throws EventFailedException {
if ( !this.registryMap.containsKey( type.getClass( ) ) ) {
this.registryMap.put( type.getClass( ), new ReentrantListenerRegistry( ) );
}
this.registryMap.get( type.getClass( ) ).fireEvent( type, e );
}
public Future<Event> fireEventAsync( final Object type, final Event e ) {
return Threads.enqueue( ServiceConfigurations.createEphemeral( Empyrean.INSTANCE ), 32, new Callable<Event>( ) {
@Override
public Event call( ) throws Exception {
try {
ListenerRegistry.this.fireEvent( type, e );
return e;
} catch ( final Exception ex ) {
Logs.exhaust( ).error( ex, ex );
throw ex;
}
}
} );
}
public static class ReentrantListenerRegistry<T> {
private Multimap<T, EventListener> listenerMap;
private Lock modificationLock;
public ReentrantListenerRegistry() {
super();
this.listenerMap = ArrayListMultimap.create();
this.modificationLock = new ReentrantLock();
}
public void register( T type, EventListener listener ) {
if ( type instanceof Enum ) {
EventRecord.caller( ReentrantListenerRegistry.class, EventType.LISTENER_REGISTERED, type.getClass().getSimpleName(), ( ( Enum ) type ).name(),
listener.getClass().getSimpleName() ).trace();
} else {
EventRecord.caller( ReentrantListenerRegistry.class, EventType.LISTENER_REGISTERED, type.getClass().getSimpleName(),
listener.getClass().getSimpleName() ).trace();
}
this.modificationLock.lock();
try {
if ( !this.listenerMap.containsEntry( type, listener ) ) {
this.listenerMap.put( type, listener );
}
} finally {
this.modificationLock.unlock();
}
}
public void deregister( T type, EventListener listener ) {
if ( type instanceof Enum ) {
EventRecord.caller( ReentrantListenerRegistry.class, EventType.LISTENER_DEREGISTERED, type.getClass().getSimpleName(), ( ( Enum ) type ).name(),
listener.getClass().getSimpleName() ).trace();
} else {
EventRecord.caller( ReentrantListenerRegistry.class, EventType.LISTENER_DEREGISTERED, type.getClass().getSimpleName(),
listener.getClass().getSimpleName() ).trace();
}
this.modificationLock.lock();
try {
this.listenerMap.remove( type, listener );
} finally {
this.modificationLock.unlock();
}
}
public void destroy( T type ) {
this.modificationLock.lock();
try {
for ( EventListener e : this.listenerMap.get( type ) ) {
EventRecord.caller( ReentrantListenerRegistry.class, EventType.LISTENER_DESTROY_ALL, type.getClass().getSimpleName(),
e.getClass().getCanonicalName() ).trace();
}
this.listenerMap.removeAll( type );
} finally {
this.modificationLock.unlock();
}
}
public void fireThrowableEvent(T type, Event e) throws EventFailedException {
List<EventListener> listeners;
this.modificationLock.lock( );
try {
listeners = Lists.newArrayList( this.listenerMap.get( type ) );
} finally {
this.modificationLock.unlock( );
}
/**
* Inline the madness that is going on here. Async execution is mutually exclusive with
* direct result propagation to the caller.
*/
List<Throwable> errors = Lists.newArrayList( );
for ( EventListener ce : listeners ) {
EventRecord.here( ReentrantListenerRegistry.class, EventType.LISTENER_EVENT_FIRED, ce.getClass().getSimpleName(), e.toString() ).trace();
try {
ce.fireEvent( e );
} catch ( Exception ex ) {
EventFailedException eventEx = new EventFailedException( "Failed to fire event: listener=" + ce.getClass( ).getCanonicalName( ) + " event="
+ e.toString() + " because of: "
+ ex.getMessage( ), Exceptions.filterStackTrace( ex ) );
throw eventEx;
}
}
}
/**
* This execution case is system critical and must run very quickly. The key system tasks run on
* the order of 10ms at the worst case. To ensure that compliant tasks are not disrupted by less
* discriminating commoner tasks, everyone must execute asynchronously while enforcing a single
* thread of execution for any particular event listener.
*/
public void fireEvent( T type, Event e ) {
List<EventListener> listeners;
this.modificationLock.lock( );
try {
listeners = Lists.newArrayList( this.listenerMap.get( type ) );
} finally {
this.modificationLock.unlock( );
}
for ( EventListener ce : listeners ) {
Threads.lookup( Empyrean.class, ListenerRegistry.class, "listenerTasks" )
.submit( listenerTasks.getUnchecked( ce ).apply( e ) );
}
}
/**
* A wrapper for event listeners which produces callables that can only be executed one at a
* time, otherwise returning immediately (by way of the atomic boolean).
*/
private static final LoadingCache<EventListener, Function<Event, Callable<Object>>> listenerTasks = CacheBuilder.newBuilder().build( getListenerWrapper() );
private static final CacheLoader<EventListener, Function<Event, Callable<Object>>> getListenerWrapper() {
return new CacheLoader<EventListener, Function<Event, Callable<Object>>>() {
@Override
public Function<Event, Callable<Object>> load( final EventListener key ) throws Exception {
return new Function<Event, Callable<Object>>() {
final AtomicBoolean busy = new AtomicBoolean( false );
@Override
public Callable<Object> apply( final Event input ) {
return new Callable<Object>() {
@Override
public Object call() throws Exception {
if ( !Ats.inClassHierarchy(input).has( Periodic.class ) || busy.compareAndSet( false, true ) ) {
try {
key.fireEvent( input );
} catch ( Exception ex ) {
EventFailedException eventEx = new EventFailedException( "Failed to fire event: listener=" + key.getClass( ).getCanonicalName( ) + " event="
+ ex.toString( ) + " because of: "
+ ex.getMessage( ), Exceptions.filterStackTrace( ex ) );
Logs.extreme( ).error( eventEx, eventEx );
LOG.error( eventEx );
} finally {
busy.set( false );
}
}
return input;
}
};
}
};
}
};
}
}
}