/*************************************************************************
* 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 static com.eucalyptus.util.Parameters.checkParam;
import static org.hamcrest.Matchers.notNullValue;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.log4j.Logger;
import com.eucalyptus.util.HasName;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
public class StatefulNamedRegistry<T extends HasName, E extends Enum<E>> {
private static Logger LOG = Logger.getLogger( StatefulNamedRegistry.class );
public static class StateEvent<T, E extends Enum<E>> extends GenericEvent<T> {
private final E state;
private final E previousState;
private final T value;
public StateEvent( T value, E state, E previousState ) {
super( );
this.value = value;
this.state = state;
this.previousState = previousState;
}
public StateEvent( T value, E state, E previousState, T message ) {
super( message );
this.value = value;
this.state = state;
this.previousState = previousState;
}
public E getState( ) {
return this.state;
}
public E getPreviousState( ) {
return this.previousState;
}
}
private class StatefulValue {
private final E state;
private final T value;
public StatefulValue( E state, T value ) {
super( );
this.state = state;
this.value = value;
}
@Override
public int hashCode( ) {
final int prime = 31;
int result = 1;
result = prime * result + ( ( this.value == null )
? 0
: this.value.hashCode( ) );
return result;
}
@Override
public boolean equals( Object obj ) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( getClass( ) != obj.getClass( ) ) {
return false;
}
StatefulValue other = ( StatefulValue ) obj;
if ( this.value == null ) {
if ( other.value != null ) {
return false;
}
} else if ( !this.value.equals( other.value ) ) {
return false;
}
return true;
}
public E getState( ) {
return this.state;
}
public T getValue( ) {
return this.value;
}
}
private final ConcurrentNavigableMap<String, StatefulValue> stateMap = new ConcurrentSkipListMap<String, StatefulValue>( );
private E[] states;
private ReadWriteLock canHas;
protected StatefulNamedRegistry( E... states ) {
super( );
this.states = states;// hack.
this.canHas = new ReentrantReadWriteLock( );
}
public boolean isRegistered( String name ) {
this.canHas.readLock( ).lock( );
try {
return this.stateMap.containsKey( name );
} finally {
this.canHas.readLock( ).unlock( );
}
}
public T deregister( String key ) {
StatefulValue oldValue = null;
this.canHas.writeLock( ).lock( );
try {
oldValue = this.stateMap.remove( key );
if ( oldValue != null ) {
return oldValue.getValue( );
} else {
throw new NoSuchElementException( "Can't find registered object: " + key + " in " + this.getClass( ).getSimpleName( ) );
}
} finally {
this.canHas.writeLock( ).unlock( );
if ( oldValue != null ) {
this.fireStateChange( oldValue, this.states[0] );
}
}
}
/**
* @param obj
* @param nextState
* @return reference to registered version of obj
*/
public T register( T obj, E nextState ) {
StatefulValue oldValue = null;
StatefulValue newValue = null;
checkParam( obj, notNullValue() );
this.canHas.writeLock( ).lock( );
try {
newValue = new StatefulValue( nextState, obj );
oldValue = this.stateMap.putIfAbsent( obj.getName( ), newValue );
if ( oldValue != null ) {
newValue = new StatefulValue( nextState, oldValue.getValue( ) );
if ( this.stateMap.replace( obj.getName( ), oldValue, newValue ) ) {
return newValue.getValue( );
} else {
return ( newValue = oldValue ).getValue( );
}
} else {
return newValue.getValue( );
}
} finally {
this.canHas.writeLock( ).unlock( );
this.fireStateChange( oldValue, nextState );
}
}
private E getState( String name ) throws NoSuchElementException {
this.canHas.readLock( ).lock( );
try {
if ( this.stateMap.containsKey( name ) ) {
return this.stateMap.get( name ).getState( );
} else {
throw new NoSuchElementException( "Can't find registered object: " + name + " in " + this.getClass( ).getSimpleName( ) );
}
} finally {
this.canHas.readLock( ).unlock( );
}
}
/**
* Returns the reference (see {@link StatefulValue#getValue()}) of the currently associated
* {@link StatefulValue} for the key {@code name}.
*
* {@inheritDoc StatefulNamedRegistry#lookupEntry(String)}
*
* @param name
* @return
* @throws NoSuchElementException
*/
public T lookup( String name ) throws NoSuchElementException {
this.canHas.readLock( ).lock( );
try {
return this.lookupEntry( name ).getValue( );
} finally {
this.canHas.readLock( ).unlock( );
}
}
/**
* Returns the reference (see {@link StatefulValue#getValue()}) of the currently associated
* {@link StatefulValue} for the key determined by {@code obj.getName()} (see
* {@link HasName#getName()}).
*
* {@inheritDoc StatefulNamedRegistry#lookupEntry(String)}
*
* @see HasName#getName()
* @see StatefulNamedRegistry#lookupEntry(String)
* @see StatefulValue#getValue()
* @param obj
* @return
* @throws NoSuchElementException
*/
public T lookup( T obj ) throws NoSuchElementException {
this.canHas.readLock( ).lock( );
try {
return this.lookupEntry( obj.getName( ) ).getValue( );
} finally {
this.canHas.readLock( ).unlock( );
}
}
public E lookupState( String name ) throws NoSuchElementException {
this.canHas.readLock( ).lock( );
try {
this.lookup( "hi" );
return this.lookupEntry( name ).getState( );
} finally {
this.canHas.readLock( ).unlock( );
}
}
/**
* Try to lookup the {@link StatefulValue} instance associated with the key {@code name}. If
* found, the registered reference is returned. If not, a {@link NoSuchElementException} is
* thrown.
*
* @see StatefulValue
* @param name
* @return
* @throws NoSuchElementException
*/
public StatefulValue lookupEntry( String name ) throws NoSuchElementException {
this.canHas.readLock( ).lock( );
try {
StatefulValue currValue = this.stateMap.get( name );
if ( currValue != null ) {
return currValue;
} else {
throw new NoSuchElementException( "Can't find registered object: " + name + " in " + this.getClass( ).getSimpleName( ) );
}
} finally {
this.canHas.readLock( ).unlock( );
}
}
public boolean setState( String name, E newState ) throws NoSuchElementException {
StatefulValue oldValue = null;
this.canHas.writeLock( ).lock( );
try {
oldValue = this.lookupEntry( name );
if ( oldValue.getState( ).equals( newState ) ) {
return true;
} else {
return this.stateMap.replace( name, oldValue, new StatefulValue( newState, oldValue.getValue( ) ) );
}
} finally {
this.canHas.writeLock( ).unlock( );
if ( oldValue != null && !oldValue.getState( ).equals( newState ) ) {
this.fireStateChange( oldValue, newState );
}
}
}
private void fireStateChange( StatefulValue oldValue, E newState ) {
try {
ListenerRegistry.getInstance( ).fireEvent( new StateEvent<T, E>( oldValue.getValue( ), newState, oldValue.getState( ) ) );
} catch ( EventFailedException e ) {
LOG.warn( "Registry change was vetoed: " + e, e );
}
}
public boolean contains( String name ) {
this.canHas.readLock( ).lock( );
try {
return this.stateMap.containsKey( name );
} finally {
this.canHas.readLock( ).unlock( );
}
}
public boolean contains( String name, E state ) {
this.canHas.readLock( ).lock( );
try {
return this.stateMap.containsKey( name ) && this.stateMap.get( state ).getState( ).equals( state );
} finally {
this.canHas.readLock( ).unlock( );
}
}
public List<T> listValues( ) {
List<T> valueList = Lists.newArrayList( );
for ( StatefulValue m : this.stateMap.values( ) ) {
valueList.add( m.getValue( ) );
}
return ImmutableList.copyOf( valueList );
}
public ImmutableList<String> listKeys( E state ) {
return ImmutableList.copyOf( this.stateMap.keySet( ) );
}
public ImmutableList<T> listStateValues( E state ) {
List<T> valueList = Lists.newArrayList( );
for ( StatefulValue m : this.stateMap.values( ) ) {
if ( m.getState( ).equals( state ) ) {
valueList.add( m.getValue( ) );
}
}
return ImmutableList.copyOf( valueList );
}
}