/* * 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.helper.SimpleLogger; import org.apache.felix.scr.impl.inject.BindParameters; import org.apache.felix.scr.impl.inject.ClassUtils; import org.apache.felix.scr.impl.inject.ValueUtils; import org.apache.felix.scr.impl.manager.ComponentContextImpl; import org.apache.felix.scr.impl.manager.RefPair; 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. * @param <S> */ 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, 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 SimpleLogger 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<List<ValueUtils.ValueType>>(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<List<ValueUtils.ValueType>>(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<List<ValueUtils.ValueType>>(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<List<ValueUtils.ValueType>>(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<List<ValueUtils.ValueType>>(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<ValueUtils.ValueType>(2); paramTypes.add(ValueUtils.ValueType.ref_serviceType); paramTypes.add(ValueUtils.ValueType.ref_map); return new MethodInfo<List<ValueUtils.ValueType>>(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<ValueUtils.ValueType>(2); paramTypes.add(ValueUtils.ValueType.ref_serviceType); paramTypes.add(ValueUtils.ValueType.ref_map); return new MethodInfo<List<ValueUtils.ValueType>>(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<ValueUtils.ValueType>(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<List<ValueUtils.ValueType>>(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}", new Object[] { targetClass.getName() }, null ); 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, SimpleLogger 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, SimpleLogger 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, SimpleLogger 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, SimpleLogger 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, SimpleLogger 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, SimpleLogger 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.getServiceObject() == null && methodExists( parameters.getComponentContext().getLogger() ) ) { if ( m_paramTypes.contains(ValueUtils.ValueType.ref_serviceType) ) { return parameters.getServiceObject(context); } } return true; } @Override protected Object[] getParameters( Method method, BindParameters bp ) { ComponentContextImpl<?> 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, null, key, refPair); } return result; } @Override protected String getMethodNamePrefix() { return "bind"; } }