blob: 214f4742e90076c8fb69270afe0e56930d61b2db [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.scr.impl;
import java.lang.reflect.*;
import java.util.*;
import org.apache.felix.scr.Reference;
import org.osgi.framework.*;
import org.osgi.service.component.ComponentInstance;
import org.osgi.service.log.LogService;
/**
* The default ComponentManager. Objects of this class are responsible for managing
* implementation object's lifecycle.
*
*/
abstract class AbstractComponentManager implements ComponentManager, ComponentInstance
{
// the ID of this component
private long m_componentId;
// The state of this instance manager
private int m_state;
// The metadata
private ComponentMetadata m_componentMetadata;
// The dependency managers that manage every dependency
private List m_dependencyManagers;
// A reference to the BundleComponentActivator
private BundleComponentActivator m_activator;
// The ServiceRegistration
private ServiceRegistration m_serviceRegistration;
// lock object used by service registration locking
private Object serviceRegistrationLock = new Object();
// the field set to the owner of the lock
private Thread serviceRegistrationLockOwner;
/**
* The constructor receives both the activator and the metadata
*
* @param activator
* @param metadata
*/
protected AbstractComponentManager( BundleComponentActivator activator, ComponentMetadata metadata, long componentId )
{
m_activator = activator;
m_componentMetadata = metadata;
m_componentId = componentId;
m_state = STATE_DISABLED;
m_dependencyManagers = new ArrayList();
log( LogService.LOG_DEBUG, "Component created", m_componentMetadata, null );
}
//---------- Asynchronous frontend to state change methods ----------------
/**
* Enables this component and - if satisfied - also activates it. If
* enabling the component fails for any reason, the component ends up
* disabled.
* <p>
* This method ignores the <i>enabled</i> flag of the component metadata
* and just enables as requested.
* <p>
* This method schedules the enablement for asynchronous execution.
*/
public final void enable()
{
getActivator().schedule( new Runnable()
{
public void run()
{
enableInternal();
}
} );
}
/**
* Enables this component and - if satisfied - also activates it
* synchronously or asynchronously. If enabling the component fails for
* any reason, the component ends up disabled.
* <p>
* This method ignores the <i>enabled</i> flag of the component metadata
* and just enables as requested.
*
* @param synchronous If <code>true</code> the component is immediately
* enabled synchronously. Otherwise the component enabled is scheduled
* for asynchronous enabled by calling {@link #enable()}.
*/
protected final void enable( boolean synchronous )
{
if ( synchronous )
{
enableInternal();
}
else
{
enable();
}
}
/**
* Activates this component if satisfied. If any of the dependencies is
* not met, the component is not activated and remains unsatisifed.
* <p>
* This method schedules the activation for asynchronous execution.
*/
public final void activate()
{
getActivator().schedule( new Runnable()
{
public void run()
{
activateInternal();
}
} );
}
/**
* Reconfigures this component by deactivating and activating it. During
* activation the new configuration data is retrieved from the Configuration
* Admin Service.
*/
public final void reconfigure()
{
log( LogService.LOG_DEBUG, "Deactivating and Activating to reconfigure", m_componentMetadata, null );
reactivateAsynchronous();
}
/**
* Cycles this component by deactivating it and - if still satisfied -
* activating it again.
* <p>
* This method immediately deactivates the component to prevent action
* with old configuration/references and schedules the reactivation for
* asynchronous execution.
*/
public final void reactivate()
{
// synchronously deactivate and schedule activation asynchronously
deactivate();
getActivator().schedule( new Runnable()
{
public void run()
{
activateInternal();
}
} );
}
/**
* Cycles this component by deactivating it and - if still satisfied -
* activating it again asynchronously.
* <p>
* This method schedules the deactivation and reactivation for asynchronous
* execution.
*/
public final void reactivateAsynchronous()
{
getActivator().schedule( new Runnable()
{
public void run()
{
deactivateInternal();
activateInternal();
}
} );
}
/**
* Deactivates the component.
* <p>
* This method unlike other state change methods immediately takes
* action and deactivates the component. The reason for this is, that this
* method is called when a required service is not available any more and
* hence the component cannot work. The exception to this is, that the
* deactivation is scheduled for asynchronous execution if the component
* is currently activating.
* <p>
* We must not immediately deactivate while the component is activating
* because we might create a deadlock: If this method is called from the
* framework service event thread some locks may be held. If at the same
* time the activation tries to access referenced services the framework
* lock will be tried to be obtained. On the other hand the activation
* holds a lock on this instance and the deactivation tries to get that
* lock.
*/
public final void deactivate()
{
if ( getState() == STATE_ACTIVATING )
{
log( LogService.LOG_INFO,
"Asynchronously deactivating the component to prevent a deadlock while it is being activated",
m_componentMetadata, null );
getActivator().schedule( new Runnable()
{
public void run()
{
deactivateInternal();
}
} );
}
else
{
deactivateInternal();
}
}
/**
* Disables this component and - if active - first deactivates it. The
* component may be reenabled by calling the {@link #enable()} method.
* <p>
* This method schedules the disablement for asynchronous execution.
*/
public final void disable()
{
getActivator().schedule( new Runnable()
{
public void run()
{
disableInternal();
}
} );
}
/**
* Disposes off this component deactivating and disabling it first as
* required. After disposing off the component, it may not be used anymore.
* <p>
* This method unlike the other state change methods immediately takes
* action and disposes the component. The reason for this is, that this
* method has to actually complete before other actions like bundle stopping
* may continue.
*/
public void dispose()
{
disposeInternal();
}
//---------- Component interface ------------------------------------------
public long getId()
{
return m_componentId;
}
public String getName()
{
return m_componentMetadata.getName();
}
public Bundle getBundle()
{
return getActivator().getBundleContext().getBundle();
}
public String getClassName()
{
return m_componentMetadata.getImplementationClassName();
}
public String getFactory()
{
return m_componentMetadata.getFactoryIdentifier();
}
public Reference[] getReferences()
{
if ( m_dependencyManagers != null && m_dependencyManagers.size() > 0 )
{
return ( org.apache.felix.scr.Reference[] ) m_dependencyManagers
.toArray( new Reference[m_dependencyManagers.size()] );
}
return null;
}
public boolean isImmediate()
{
return m_componentMetadata.isImmediate();
}
public boolean isDefaultEnabled()
{
return m_componentMetadata.isEnabled();
}
public boolean isServiceFactory()
{
return m_componentMetadata.getServiceMetadata() != null
&& m_componentMetadata.getServiceMetadata().isServiceFactory();
}
public String[] getServices()
{
if ( m_componentMetadata.getServiceMetadata() != null )
{
return m_componentMetadata.getServiceMetadata().getProvides();
}
return null;
}
//---------- internal immediate state change methods ----------------------
// these methods must only be called from a separate thread by calling
// the respective asynchronous (public) method
/**
* Enable this component
*
*/
private void enableInternal()
{
if ( getState() == STATE_DESTROYED )
{
log( LogService.LOG_ERROR, "Destroyed Component cannot be enabled", m_componentMetadata, null );
return;
}
else if ( getState() != STATE_DISABLED )
{
log( LogService.LOG_DEBUG, "Component is already enabled", m_componentMetadata, null );
return;
}
log( LogService.LOG_DEBUG, "Enabling component", m_componentMetadata, null );
try
{
// If this component has got dependencies, create dependency managers for each one of them.
if ( m_componentMetadata.getDependencies().size() != 0 )
{
Iterator dependencyit = m_componentMetadata.getDependencies().iterator();
while ( dependencyit.hasNext() )
{
ReferenceMetadata currentdependency = ( ReferenceMetadata ) dependencyit.next();
DependencyManager depmanager = new DependencyManager( this, currentdependency );
m_dependencyManagers.add( depmanager );
}
}
// enter enabled state before trying to activate
setState( STATE_ENABLED );
log( LogService.LOG_DEBUG, "Component enabled", m_componentMetadata, null );
// immediately activate the compopnent, no need to schedule again
activateInternal();
}
catch ( Exception ex )
{
log( LogService.LOG_ERROR, "Failed enabling Component", m_componentMetadata, ex );
// ensure we get back to DISABLED state
// immediately disable, no need to schedule again
disableInternal();
}
}
/**
* Activate this Instance manager.
*
* 112.5.6 Activating a component configuration consists of the following steps
* 1. Load the component implementation class
* 2. Create the component instance and component context
* 3. Bind the target services
* 4. Call the activate method, if present
* [5. Register provided services]
*/
private void activateInternal()
{
synchronized ( this )
{
// CONCURRENCY NOTE: This method is only called from within the
// ComponentActorThread to enable, activate or reactivate the
// component. Still we use the setStateConditional to not create
// a race condition with the deactivateInternal method
if ( !setStateConditional( STATE_ENABLED | STATE_UNSATISFIED, STATE_ACTIVATING ) )
{
return;
}
// we cannot activate if the component activator is shutting down
if ( !isActive() )
{
log( LogService.LOG_DEBUG, "Component cannot be activated because the Activator is being disposed",
m_componentMetadata, null );
setState( STATE_UNSATISFIED );
return;
}
log( LogService.LOG_DEBUG, "Activating component", m_componentMetadata, null );
// Before creating the implementation object, we are going to
// test if all the mandatory dependencies are satisfied
Dictionary properties = getProperties();
Iterator it = m_dependencyManagers.iterator();
while ( it.hasNext() )
{
DependencyManager dm = ( DependencyManager ) it.next();
// ensure the target filter is correctly set
dm.setTargetFilter( properties );
// check whether the service is satisfied
if ( !dm.isSatisfied() )
{
// at least one dependency is not satisfied
log( LogService.LOG_INFO, "Dependency not satisfied: " + dm.getName(), m_componentMetadata, null );
setState( STATE_UNSATISFIED );
}
// if at least one dependency is missing, we cannot continue and
// have to return
if ( getState() == STATE_UNSATISFIED )
{
return;
}
}
// 1. Load the component implementation class
// 2. Create the component instance and component context
// 3. Bind the target services
// 4. Call the activate method, if present
if ( !createComponent() )
{
// component creation failed, not active now
log( LogService.LOG_ERROR, "Component instance could not be created, activation failed",
m_componentMetadata, null );
// set state to unsatisfied
setState( STATE_UNSATISFIED );
return;
}
// set the service registration guard before we actually set our
// state to satisfied. If we would set this after setting the
// state to satisified, there would be a theoretical window
// between this state setting and service locking
lockServiceRegistration();
// Validation occurs before the services are provided, otherwhise the
// service provider's service may be called by a service requester
// while it is still ACTIVATING
setState( getSatisfiedState() );
}
// 5. Register provided services
// call this outside of the synchronization to prevent a possible
// deadlock if service registration tries to lock the framework or
// owning bundle during registration (see FELIX-384)
try
{
m_serviceRegistration = registerComponentService();
log( LogService.LOG_DEBUG, "Component activated", m_componentMetadata, null );
}
catch ( IllegalStateException ise )
{
// thrown by service registration if the bundle is stopping
// we just log this at debug level but ignore it
log( LogService.LOG_DEBUG, "Component activation failed while registering the service",
m_componentMetadata, ise );
}
finally
{
// reset the service registration guard
unlockServiceRegistration();
}
}
/**
* This method deactivates the manager, performing the following steps
*
* [0. Remove published services from the registry]
* 1. Call the deactivate() method, if present
* 2. Unbind any bound services
* 3. Release references to the component instance and component context
**/
private synchronized void deactivateInternal()
{
// CONCURRENCY NOTE: This method may be called either from the
// ComponentActorThread to handle application induced disabling or
// as a result of an unsatisfied service dependency leading to
// component deactivation. We therefore have to guard against
// paralell state changes.
if ( !setStateConditional( STATE_ACTIVATING | STATE_ACTIVE | STATE_REGISTERED | STATE_FACTORY,
STATE_DEACTIVATING ) )
{
return;
}
log( LogService.LOG_DEBUG, "Deactivating component", m_componentMetadata, null );
// 0.- Remove published services from the registry
unregisterComponentService();
// 1.- Call the deactivate method, if present
// 2. Unbind any bound services
// 3. Release references to the component instance and component context
deleteComponent();
//Activator.trace("InstanceManager from bundle ["+ m_activator.getBundleContext().getBundle().getBundleId() + "] was invalidated.");
// reset to state UNSATISFIED
setState( STATE_UNSATISFIED );
log( LogService.LOG_DEBUG, "Component deactivated", m_componentMetadata, null );
}
private void disableInternal()
{
// CONCURRENCY NOTE: This method is only called from the BundleComponentActivator or by application logic
// but not by the dependency managers
// if the component is already disabled or destroyed, we have
// nothing to do
if ( ( getState() & ( STATE_DISABLED | STATE_DESTROYED ) ) != 0 )
{
log( LogService.LOG_DEBUG, "Component already disabled", m_componentMetadata, null );
return;
}
// deactivate first, this does nothing if not active/registered/factory
deactivateInternal();
log( LogService.LOG_DEBUG, "Disabling component", m_componentMetadata, null );
// close all service listeners now, they are recreated on enable
// Stop the dependency managers to listen to events...
Iterator it = m_dependencyManagers.iterator();
while ( it.hasNext() )
{
DependencyManager dm = ( DependencyManager ) it.next();
dm.close();
}
m_dependencyManagers.clear();
// we are now disabled, ready for re-enablement or complete destroyal
setState( STATE_DISABLED );
log( LogService.LOG_DEBUG, "Component disabled", m_componentMetadata, null );
}
/**
*
*/
private void disposeInternal()
{
// CONCURRENCY NOTE: This method is only called from the BundleComponentActivator or by application logic
// but not by the dependency managers
// if the component is already destroyed, we have nothing to do
if ( getState() == STATE_DESTROYED )
{
log( LogService.LOG_DEBUG, "Component already destroyed", m_componentMetadata, null );
return;
}
// disable first to clean up correctly
disableInternal();
// this component must not be used any more
setState( STATE_DESTROYED );
log( LogService.LOG_DEBUG, "Component disposed", m_componentMetadata, null );
// release references (except component metadata for logging purposes)
m_activator = null;
m_dependencyManagers = null;
}
//---------- Component handling methods ----------------------------------
/**
* Method is called by {@link #activate()} in STATE_ACTIVATING or by
* {@link DelayedComponentManager#getService(Bundle, ServiceRegistration)}
* in STATE_REGISTERED.
*
* @return <code>true</code> if creation of the component succeeded. If
* <code>false</code> is returned, the cause should have been logged.
*/
protected abstract boolean createComponent();
/**
* Method is called by {@link #deactivate()} in STATE_DEACTIVATING
*
*/
protected abstract void deleteComponent();
/**
* Returns the service object to be registered if the service element is
* specified.
* <p>
* Extensions of this class may overwrite this method to return a
* ServiceFactory to register in the case of a delayed or a service
* factory component.
*/
protected abstract Object getService();
/**
* Returns the state value to set, when the component is satisfied. The
* return value depends on the kind of the component:
* <dl>
* <dt>Immediate</dt><dd><code>STATE_ACTIVE</code></dd>
* <dt>Delayed</dt><dd><code>STATE_REGISTERED</code></dd>
* <dt>Component Factory</dt><dd><code>STATE_FACTORY</code></dd>
* </dl>
*
* @return
*/
private int getSatisfiedState()
{
if ( m_componentMetadata.isFactory() )
{
return STATE_FACTORY;
}
else if ( m_componentMetadata.isImmediate() )
{
return STATE_ACTIVE;
}
else
{
return STATE_REGISTERED;
}
}
// 5. Register provided services
protected ServiceRegistration registerComponentService()
{
if ( getComponentMetadata().getServiceMetadata() != null )
{
log( LogService.LOG_DEBUG, "registering services", getComponentMetadata(), null );
// get a copy of the component properties as service properties
Dictionary serviceProperties = copyTo( null, getProperties() );
return getActivator().getBundleContext().registerService(
getComponentMetadata().getServiceMetadata().getProvides(), getService(), serviceProperties );
}
return null;
}
protected void unregisterComponentService()
{
// outside of try-finally to not trigger inadvertend unlock
lockServiceRegistration();
try
{
if ( m_serviceRegistration != null )
{
m_serviceRegistration.unregister();
m_serviceRegistration = null;
log( LogService.LOG_DEBUG, "unregistering the services", getComponentMetadata(), null );
}
}
finally
{
unlockServiceRegistration();
}
}
//----------- Service Registration Locking --------------------------------
// Implementation note for service registration locking
// ----------------------------------------------------
// The activateInternal method sets the component state to satisfied (aka
// ready) before the component is registered as a service and the internal
// service registration field is set.
// If now in the time between setting the state to satisified and the
// service registration field being set, the component is deactivated --
// possibly through reconfiguration -- the service may not be unregistered
// because the field is not set, but re-activation of the component may
// register the service again thus resulting in two services being
// registered, the active and the deactivated. Which of both is being used
// is framework implementation dependent but chances are, the wrong is
// used resulting in system failure.
// To fix this, all access to the service registration field is guarded by
// a lock. Only if a thread is able to set the lock flag, can the service
// registration field be accessed.
// To circumvent the above mentioned situation, the activateInternal method
// locks the field _before_ setting the component satisfied. This prevents
// the deactivateInternal method from deactivating the component until the
// service has been registered and the lock been freed. Only then can the
// deactivateInternal method start its deactivation task by unregistering
// the service.
// See FELIX-550 for more information.
// locks service registration by waiting for the registration to not
// be locked and then locking it
private void lockServiceRegistration()
{
synchronized ( serviceRegistrationLock )
{
if ( serviceRegistrationLockOwner != null )
{
log( LogService.LOG_INFO, "Waiting for Service Registration owned by " + serviceRegistrationLockOwner,
m_componentMetadata, null );
int waitGuard = 10;
while ( serviceRegistrationLockOwner != null && waitGuard > 0 )
{
// wait at most one second
try
{
serviceRegistrationLock.wait( 1000L );
}
catch ( InterruptedException ie )
{
// don't care
}
waitGuard--;
}
// timedout waiting for the service registration lock
if ( waitGuard <= 0 )
{
throw new IllegalStateException( "Cannot get Service Registration, owned by "
+ serviceRegistrationLockOwner );
}
log( LogService.LOG_INFO, "Service Registration is now ready", m_componentMetadata, null );
}
serviceRegistrationLockOwner = Thread.currentThread();
}
}
// unlocks the service registration. This should only be called by the
// thread actually holding the lock.
private void unlockServiceRegistration()
{
synchronized ( serviceRegistrationLock )
{
Thread current = Thread.currentThread();
if ( serviceRegistrationLockOwner == current )
{
serviceRegistrationLockOwner = null;
// notify threads waiting to lock service registration
serviceRegistrationLock.notifyAll();
}
else
{
log( LogService.LOG_DEBUG, "Not releasing Service Registration by " + current + ", owner is "
+ serviceRegistrationLockOwner, m_componentMetadata, null );
}
}
}
//**********************************************************************************************************
BundleComponentActivator getActivator()
{
return m_activator;
}
void log( int level, String message, ComponentMetadata metadata, Throwable ex )
{
BundleComponentActivator activator = getActivator();
if ( activator != null )
{
activator.log( level, message, metadata, ex );
}
}
/**
* Returns <code>true</code> if this instance has not been disposed off
* yet and the BundleComponentActivator is still active. If the Bundle
* Component Activator is being disposed off as a result of stopping the
* owning bundle, this method returns <code>false</code>.
*/
private boolean isActive()
{
BundleComponentActivator bca = getActivator();
return bca != null && bca.isActive();
}
Iterator getDependencyManagers()
{
return m_dependencyManagers.iterator();
}
DependencyManager getDependencyManager( String name )
{
Iterator it = getDependencyManagers();
while ( it.hasNext() )
{
DependencyManager dm = ( DependencyManager ) it.next();
if ( name.equals( dm.getName() ) )
{
return dm;
}
}
// not found
return null;
}
/**
* Get the object that is implementing this descriptor
*
* @return the object that implements the services
*/
public abstract Object getInstance();
public abstract Dictionary getProperties();
/**
* Copies the properties from the <code>source</code> <code>Dictionary</code>
* into the <code>target</code> <code>Dictionary</code>.
*
* @param target The <code>Dictionary</code> into which to copy the
* properties. If <code>null</code> a new <code>Hashtable</code> is
* created.
* @param source The <code>Dictionary</code> providing the properties to
* copy. If <code>null</code> or empty, nothing is copied.
*
* @return The <code>target</code> is returned, which may be empty if
* <code>source</code> is <code>null</code> or empty and
* <code>target</code> was <code>null</code>.
*/
protected Dictionary copyTo( Dictionary target, Dictionary source )
{
if ( target == null )
{
target = new Hashtable();
}
if ( source != null && !source.isEmpty() )
{
for ( Enumeration ce = source.keys(); ce.hasMoreElements(); )
{
Object key = ce.nextElement();
target.put( key, source.get( key ) );
}
}
return target;
}
ServiceReference getServiceReference()
{
// outside of try-finally to not trigger inadvertend unlock
lockServiceRegistration();
try
{
return ( m_serviceRegistration != null ) ? m_serviceRegistration.getReference() : null;
}
finally
{
unlockServiceRegistration();
}
}
/**
*
*/
public ComponentMetadata getComponentMetadata()
{
return m_componentMetadata;
}
public int getState()
{
return m_state;
}
/**
* sets the state of the manager
**/
protected synchronized void setState( int newState )
{
log( LogService.LOG_DEBUG,
"State transition : " + stateToString( m_state ) + " -> " + stateToString( newState ), m_componentMetadata,
null );
m_state = newState;
}
/**
* If the state is currently one of the <code>requiredStates</code>, the
* state is set to <code>newState</code> and <code>true</code> is returned.
* Otherwise the state is not changed and <code>false</code> is returned.
* <p>
* This method atomically checks the current state and sets the new state.
*
* @param requiredStates The set of states required for the state change to
* happen.
* @param newState The new state to go into.
* @return <code>true</code> if the state was one of the required states and
* the new state has now been entered.
*/
protected synchronized boolean setStateConditional( int requiredStates, int newState )
{
if ( ( getState() & requiredStates ) != 0 )
{
setState( newState );
return true;
}
return false;
}
public String stateToString( int state )
{
switch ( state )
{
case STATE_DESTROYED:
return "Destroyed";
case STATE_DISABLED:
return "Disabled";
case STATE_ENABLED:
return "Enabled";
case STATE_UNSATISFIED:
return "Unsatisfied";
case STATE_ACTIVATING:
return "Activating";
case STATE_ACTIVE:
return "Active";
case STATE_REGISTERED:
return "Registered";
case STATE_FACTORY:
return "Factory";
case STATE_DEACTIVATING:
return "Deactivating";
default:
return String.valueOf( state );
}
}
/**
* Finds the named public or protected method in the given class or any
* super class. If such a method is found, its accessibility is enfored by
* calling the <code>Method.setAccessible</code> method if required and
* the method is returned. Enforcing accessibility is required to support
* invocation of protected methods.
*
* @param clazz The <code>Class</code> which provides the method.
* @param name The name of the method.
* @param parameterTypes The parameters to the method. Passing
* <code>null</code> is equivalent to using an empty array.
* @param only Whether to only look at the declared methods of the given
* class or also inspect the super classes.
*
* @return The named method with enforced accessibility
*
* @throws NoSuchMethodException If no public or protected method with
* the given name can be found in the class or any of its super classes.
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to access the desired method.
*/
static Method getMethod( Class clazz, String name, Class[] parameterTypes, boolean only )
throws NoSuchMethodException, InvocationTargetException
{
for ( ; clazz != null; clazz = clazz.getSuperclass() )
{
try
{
// find the declared method in this class
Method method = clazz.getDeclaredMethod( name, parameterTypes );
// accept public and protected methods only and ensure accessibility
if ( Modifier.isPublic( method.getModifiers() ) || Modifier.isProtected( method.getModifiers() ) )
{
method.setAccessible( true );
return method;
}
// if only the clazz is to be scanned terminate here
if ( only )
{
break;
}
}
catch ( NoSuchMethodException nsme )
{
// ignore for now
}
catch ( Throwable throwable )
{
// unexpected problem accessing the method, don't let everything
// blow up in this situation, just throw a declared exception
throw new InvocationTargetException( throwable, "Unexpected problem trying to get method " + name );
}
}
// walked up the complete super class hierarchy and still not found
// anything, sigh ...
throw new NoSuchMethodException( name );
}
}