blob: b06649a4c2ff3a13f051fe657c7870d8465ffae0 [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.inject.methods;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.felix.scr.impl.inject.BindParameters;
import org.apache.felix.scr.impl.inject.RefPair;
import org.apache.felix.scr.impl.inject.ScrComponentContext;
import org.apache.felix.scr.impl.inject.ValueUtils;
import org.apache.felix.scr.impl.inject.internal.ClassUtils;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.metadata.DSVersion;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
/**
* Component method to be invoked on service (un)binding.
*/
public class BindMethod extends BaseMethod<BindParameters, List<ValueUtils.ValueType>>
implements org.apache.felix.scr.impl.inject.ReferenceMethod
{
private final String m_referenceClassName;
//initialized for cases where there is no method.
private volatile List<ValueUtils.ValueType> m_paramTypes = Collections.emptyList();
public BindMethod( final String methodName,
final Class<?> componentClass,
final String referenceClassName,
final DSVersion dsVersion,
final boolean configurableServiceProperties )
{
super( methodName, methodName != null, componentClass, dsVersion, configurableServiceProperties );
m_referenceClassName = referenceClassName;
}
/**
* Finds the method named in the {@link #m_methodName} field in the given
* <code>targetClass</code>. If the target class has no acceptable method
* the class hierarchy is traversed until a method is found or the root
* of the class hierarchy is reached without finding a method.
*
*
* @param targetClass The class in which to look for the method
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class or any super class.
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to find the requested method.
*/
@Override
protected MethodInfo<List<ValueUtils.ValueType>> doFindMethod( final Class<?> targetClass,
final boolean acceptPrivate,
final boolean acceptPackage,
final ComponentLogger logger )
throws SuitableMethodNotAccessibleException, InvocationTargetException
{
// 112.3.1 The method is searched for using the following priority
// 1 - ServiceReference single parameter
// 2 - DS 1.3+ : ComponentServiceObjects single parameter
// 3 - Service object single parameter
// 4 - Service interface assignment compatible single parameter
// 5 - DS 1.3+ : Single argument with Map
// 6 - DS 1.1/DS 1.2 : two parameters, first the type of or assignment compatible with the service, the second Map
// 7 - DS 1.3+ : one or more parameters of types ServiceReference, ServiceObjects, interface type,
// or assignment compatible to interface type, in any order.
// flag indicating a suitable but inaccessible method has been found
boolean suitableMethodNotAccessible = false;
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG,
"doFindMethod: Looking for method " + targetClass.getName() + "." + getMethodName(), null );
}
// Case 1 - Service reference parameter
Method method;
try
{
method = getServiceReferenceMethod( targetClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
return new MethodInfo<>(method, Collections.singletonList(ValueUtils.ValueType.ref_serviceReference));
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
// Case 2 - ComponentServiceObjects parameter
if ( getDSVersion().isDS13() )
{
try
{
method = getComponentObjectsMethod( targetClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
return new MethodInfo<>(method, Collections.singletonList(ValueUtils.ValueType.ref_serviceObjects));
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
}
// for further methods we need the class of the service object
final Class<?> parameterClass = ClassUtils.getClassFromComponentClassLoader( targetClass, m_referenceClassName, logger );
if ( parameterClass != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log(
LogService.LOG_DEBUG,
"doFindMethod: No method taking ServiceReference found, checking method taking "
+ parameterClass.getName(), null );
}
// Case 3 - Service object parameter
try
{
method = getServiceObjectMethod( targetClass, parameterClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
return new MethodInfo<>(method,
Collections.singletonList(ValueUtils.ValueType.ref_serviceType));
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
// Case 4 - Service interface assignment compatible methods
try
{
method = getServiceObjectAssignableMethod( targetClass, parameterClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
return new MethodInfo<>(method,
Collections.singletonList(ValueUtils.ValueType.ref_serviceType));
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
// Case 5 - DS 1.3+ : Single argument with Map
if ( getDSVersion().isDS13() )
{
try
{
method = getMapMethod( targetClass, parameterClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
return new MethodInfo<>(method,
Collections.singletonList(ValueUtils.ValueType.ref_map));
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
}
// signatures taking a map are only supported starting with DS 1.1
if ( getDSVersion().isDS11() && !getDSVersion().isDS13() )
{
// Case 6 - same as case 3, but + Map param (DS 1.1 only)
try
{
method = getServiceObjectWithMapMethod( targetClass, parameterClass, acceptPrivate, acceptPackage, logger );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
List<ValueUtils.ValueType> paramTypes = new ArrayList<>(2);
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
paramTypes.add(ValueUtils.ValueType.ref_map);
return new MethodInfo<>(method, paramTypes);
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
// Case 6 - same as case 4, but + Map param (DS 1.1 only)
try
{
method = getServiceObjectAssignableWithMapMethod( targetClass, parameterClass, acceptPrivate,
acceptPackage );
if ( method != null )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + method, null );
}
List<ValueUtils.ValueType> paramTypes = new ArrayList<>(2);
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
paramTypes.add(ValueUtils.ValueType.ref_map);
return new MethodInfo<>(method,
paramTypes);
}
}
catch ( SuitableMethodNotAccessibleException ex )
{
suitableMethodNotAccessible = true;
}
}
// Case 7 - Multiple parameters
if ( getDSVersion().isDS13() )
{
for (Method m: targetClass.getDeclaredMethods())
{
if (getMethodName().equals(m.getName())) {
Class<?>[] parameterTypes = m.getParameterTypes();
boolean matches = true;
boolean specialMatch = true;
List<ValueUtils.ValueType> paramTypes = new ArrayList<>(parameterTypes.length);
for (Class<?> paramType: parameterTypes) {
if (paramType == ClassUtils.SERVICE_REFERENCE_CLASS)
{
if (specialMatch && parameterClass == ClassUtils.SERVICE_REFERENCE_CLASS)
{
specialMatch = false;
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
}
else
{
paramTypes.add(ValueUtils.ValueType.ref_serviceReference);
}
}
else if (paramType == ClassUtils.COMPONENTS_SERVICE_OBJECTS_CLASS)
{
if (specialMatch && parameterClass == ClassUtils.COMPONENTS_SERVICE_OBJECTS_CLASS)
{
specialMatch = false;
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
}
else
{
paramTypes.add(ValueUtils.ValueType.ref_serviceObjects);
}
}
else if (paramType == ClassUtils.MAP_CLASS)
{
if (specialMatch && parameterClass == ClassUtils.MAP_CLASS)
{
specialMatch = false;
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
}
else
{
paramTypes.add(ValueUtils.ValueType.ref_map);
}
}
else if (paramType.isAssignableFrom( parameterClass ) )
{
paramTypes.add(ValueUtils.ValueType.ref_serviceType);
}
// DS 1.4 : Logger and FormattedLogger
else if ( getDSVersion().isDS14() && ClassUtils.LOGGER_FACTORY_CLASS.equals(m_referenceClassName) )
{
if ( paramType.getName().equals(ClassUtils.LOGGER_CLASS) )
{
paramTypes.add(ValueUtils.ValueType.ref_logger);
}
else if ( paramType.getName().equals(ClassUtils.FORMATTER_LOGGER_CLASS) )
{
paramTypes.add(ValueUtils.ValueType.ref_formatterLogger);
}
else
{
matches = false;
break;
}
}
else
{
matches = false;
break;
}
}
if (matches)
{
if ( accept( m, acceptPrivate, acceptPackage, returnValue() ) )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "doFindMethod: Found Method " + m, null );
}
return new MethodInfo<>(m, paramTypes);
}
suitableMethodNotAccessible = true;
}
}
}
}
}
else if ( logger.isLogEnabled( LogService.LOG_WARNING ) )
{
logger.log(
LogService.LOG_WARNING,
"doFindMethod: Cannot check for methods taking parameter class " + m_referenceClassName + ": "
+ targetClass.getName() + " does not see it", null );
}
// if at least one suitable method could be found but none of
// the suitable methods are accessible, we have to terminate
if ( suitableMethodNotAccessible )
{
logger.log( LogService.LOG_ERROR,
"doFindMethod: Suitable but non-accessible method found in class {0}",null,
targetClass.getName() );
throw new SuitableMethodNotAccessibleException();
}
// no method found
return null;
}
@Override
protected void setTypes(List<ValueUtils.ValueType> types)
{
m_paramTypes = types;
}
/**
* Returns a method taking a single <code>ServiceReference</code> object
* as a parameter or <code>null</code> if no such method exists.
*
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to find the requested method.
*/
private Method getServiceReferenceMethod( final Class<?> targetClass, boolean acceptPrivate, boolean acceptPackage, ComponentLogger logger )
throws SuitableMethodNotAccessibleException, InvocationTargetException
{
return getMethod( targetClass, getMethodName(), new Class[]
{ ClassUtils.SERVICE_REFERENCE_CLASS }, acceptPrivate, acceptPackage, logger );
}
private Method getComponentObjectsMethod( final Class<?> targetClass, boolean acceptPrivate, boolean acceptPackage, ComponentLogger logger )
throws SuitableMethodNotAccessibleException, InvocationTargetException
{
return getMethod(targetClass, getMethodName(),
new Class[] { ClassUtils.COMPONENTS_SERVICE_OBJECTS_CLASS }, acceptPrivate, acceptPackage,
logger);
}
/**
* Returns a method taking a single parameter of the exact type declared
* for the service reference or <code>null</code> if no such method exists.
*
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to find the requested method.
*/
private Method getServiceObjectMethod( final Class<?> targetClass, final Class<?> parameterClass, boolean acceptPrivate,
boolean acceptPackage, ComponentLogger logger ) throws SuitableMethodNotAccessibleException, InvocationTargetException
{
return getMethod( targetClass, getMethodName(), new Class[]
{ parameterClass }, acceptPrivate, acceptPackage, logger );
}
/**
* Returns a method taking a single object whose type is assignment
* compatible with the declared service type or <code>null</code> if no
* such method exists.
*
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
*/
private Method getServiceObjectAssignableMethod( final Class<?> targetClass, final Class<?> parameterClass,
boolean acceptPrivate, boolean acceptPackage, ComponentLogger logger ) throws SuitableMethodNotAccessibleException
{
// Get all potential bind methods
Method candidateBindMethods[] = targetClass.getDeclaredMethods();
boolean suitableNotAccessible = false;
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log(
LogService.LOG_DEBUG,
"getServiceObjectAssignableMethod: Checking " + candidateBindMethods.length
+ " declared method in class " + targetClass.getName(), null );
}
// Iterate over them
for ( int i = 0; i < candidateBindMethods.length; i++ )
{
Method method = candidateBindMethods[i];
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "getServiceObjectAssignableMethod: Checking " + method, null );
}
// Get the parameters for the current method
Class<?>[] parameters = method.getParameterTypes();
// Select only the methods that receive a single
// parameter
// and a matching name
if ( parameters.length == 1 && method.getName().equals( getMethodName() ) )
{
if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log( LogService.LOG_DEBUG, "getServiceObjectAssignableMethod: Considering " + method, null );
}
// Get the parameter type
final Class<?> theParameter = parameters[0];
// Check if the parameter type is ServiceReference
// or is assignable from the type specified by the
// reference's interface attribute
if ( theParameter.isAssignableFrom( parameterClass ) )
{
if ( accept( method, acceptPrivate, acceptPackage, false ) )
{
return method;
}
// suitable method is not accessible, flag for exception
suitableNotAccessible = true;
}
else if ( logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
logger.log(
LogService.LOG_DEBUG,
"getServiceObjectAssignableMethod: Parameter failure: Required " + theParameter + "; actual "
+ parameterClass.getName(), null );
}
}
}
// if one or more suitable methods which are not accessible is/are
// found an exception is thrown
if ( suitableNotAccessible )
{
throw new SuitableMethodNotAccessibleException();
}
// no method with assignment compatible argument found
return null;
}
/**
* Returns a method taking two parameters, the first being of the exact
* type declared for the service reference and the second being a
* <code>Map</code> or <code>null</code> if no such method exists.
*
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to find the requested method.
*/
private Method getServiceObjectWithMapMethod( final Class<?> targetClass, final Class<?> parameterClass,
boolean acceptPrivate, boolean acceptPackage, ComponentLogger logger ) throws SuitableMethodNotAccessibleException,
InvocationTargetException
{
return getMethod( targetClass, getMethodName(), new Class[]
{ parameterClass, ClassUtils.MAP_CLASS }, acceptPrivate, acceptPackage, logger );
}
/**
* Returns a method taking two parameters, the first being an object
* whose type is assignment compatible with the declared service type and
* the second being a <code>Map</code> or <code>null</code> if no such
* method exists.
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
*/
private Method getServiceObjectAssignableWithMapMethod( final Class<?> targetClass, final Class<?> parameterClass,
boolean acceptPrivate, boolean acceptPackage ) throws SuitableMethodNotAccessibleException
{
// Get all potential bind methods
Method candidateBindMethods[] = targetClass.getDeclaredMethods();
boolean suitableNotAccessible = false;
// Iterate over them
for ( int i = 0; i < candidateBindMethods.length; i++ )
{
final Method method = candidateBindMethods[i];
final Class<?>[] parameters = method.getParameterTypes();
if ( parameters.length == 2 && method.getName().equals( getMethodName() ) )
{
// parameters must be refclass,map
if ( parameters[0].isAssignableFrom( parameterClass ) && parameters[1] == ClassUtils.MAP_CLASS )
{
if ( accept( method, acceptPrivate, acceptPackage, false ) )
{
return method;
}
// suitable method is not accessible, flag for exception
suitableNotAccessible = true;
}
}
}
// if one or more suitable methods which are not accessible is/are
// found an exception is thrown
if ( suitableNotAccessible )
{
throw new SuitableMethodNotAccessibleException();
}
// no method with assignment compatible argument found
return null;
}
/**
* Returns a method taking a single map parameter
* or <code>null</code> if no such method exists.
*
*
* @param targetClass The class in which to look for the method. Only this
* class is searched for the method.
* @param acceptPrivate <code>true</code> if private methods should be
* considered.
* @param acceptPackage <code>true</code> if package private methods should
* be considered.
* @param logger
* @return The requested method or <code>null</code> if no acceptable method
* can be found in the target class.
* @throws SuitableMethodNotAccessibleException If a suitable method was
* found which is not accessible
* @throws InvocationTargetException If an unexpected Throwable is caught
* trying to find the requested method.
*/
private Method getMapMethod( final Class<?> targetClass, final Class<?> parameterClass,
boolean acceptPrivate, boolean acceptPackage, ComponentLogger logger ) throws SuitableMethodNotAccessibleException,
InvocationTargetException
{
return getMethod( targetClass, getMethodName(), new Class[]
{ ClassUtils.MAP_CLASS }, acceptPrivate, acceptPackage, logger );
}
@Override
public <S, T> boolean getServiceObject( final BindParameters parameters, BundleContext context )
{
//??? this resolves which we need.... better way?
if (parameters.getRefPair().getServiceObject(parameters.getComponentContext()) == null
&& methodExists(parameters.getComponentContext().getLogger()))
{
if ( m_paramTypes.contains(ValueUtils.ValueType.ref_serviceType)
|| m_paramTypes.contains(ValueUtils.ValueType.ref_logger)
|| m_paramTypes.contains(ValueUtils.ValueType.ref_formatterLogger)) {
return parameters.getRefPair().getServiceObject(parameters.getComponentContext(), context);
}
}
return true;
}
@Override
protected Object[] getParameters( Method method, BindParameters bp )
{
ScrComponentContext key = bp.getComponentContext();
Object[] result = new Object[ m_paramTypes.size()];
RefPair<?, ?> refPair = bp.getRefPair();
int i = 0;
for ( ValueUtils.ValueType pt: m_paramTypes )
{
result[i] = ValueUtils.getValue(getComponentClass().getName(), pt, method.getParameterTypes()[i], key, refPair);
i++;
}
return result;
}
@Override
protected String getMethodNamePrefix()
{
return "bind";
}
}