blob: 673a7060b2f8be603069765a7054d840bc523c42 [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.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.felix.scr.impl.inject.ComponentConstructor;
import org.apache.felix.scr.impl.inject.OpenStatus;
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.ValueUtils.ValueType;
import org.apache.felix.scr.impl.inject.field.FieldUtils;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.osgi.service.log.LogService;
/**
* This implementation is used to construct a component instance object,
* call the constructor and set the activation fields.
*/
public class ComponentConstructorImpl<S> implements ComponentConstructor<S>
{
private final Field[] activationFields;
private final ValueType[] activationFieldTypes;
private final Constructor<S> constructor;
private final ValueType[] constructorArgTypes;
private final ReferenceMetadata[] constructorRefs;
@SuppressWarnings("unchecked")
public ComponentConstructorImpl(final ComponentMetadata componentMetadata,
final Class<S> componentClass,
final ComponentLogger logger)
{
// constructor injection
// get reference parameter map
final Map<Integer, List<ReferenceMetadata>> paramMap = ( componentMetadata.getNumberOfConstructorParameters() > 0 ? new HashMap<Integer, List<ReferenceMetadata>>() : null);
for ( final ReferenceMetadata refMetadata : componentMetadata.getDependencies())
{
if ( refMetadata.getParameterIndex() != null )
{
final int index = refMetadata.getParameterIndex();
if ( index > componentMetadata.getNumberOfConstructorParameters() )
{
// if the index (starting at 0) is equal or higher than the number of constructor arguments
// we log an error and ignore the reference
logger.log(LogService.LOG_ERROR,
"Ignoring reference {0} for constructor injection. Parameter index is too high.", null,
refMetadata.getName() );
}
else if ( !refMetadata.isStatic() )
{
// if the reference is dynamic, we log an error and ignore the reference
logger.log(LogService.LOG_ERROR,
"Ignoring reference {0} for constructor injection. Reference is dynamic.", null,
refMetadata.getName() );
}
List<ReferenceMetadata> list = paramMap.get(index);
if ( list == null )
{
list = new ArrayList<>();
paramMap.put(index, list);
}
list.add(refMetadata);
}
}
// Search constructor
Constructor<S> found = null;
ValueType[] foundTypes = null;
ReferenceMetadata[] foundRefs = null;
final Constructor<?>[] constructors = componentClass.getConstructors();
for(final Constructor<?> c : constructors)
{
// we try each constructor with the right number of arguments
if ( c.getParameterTypes().length == componentMetadata.getNumberOfConstructorParameters() )
{
final Constructor<S> check = (Constructor<S>) c;
logger.log(LogService.LOG_DEBUG,
"Checking constructor {0}", null,
check );
// check argument types
if ( componentMetadata.getNumberOfConstructorParameters() > 0 )
{
boolean hasFailure = false;
final Class<?>[] argTypes = check.getParameterTypes();
foundTypes = new ValueType[argTypes.length];
foundRefs = new ReferenceMetadata[argTypes.length];
for(int i=0; i<foundTypes.length;i++)
{
final List<ReferenceMetadata> refs = paramMap.get(i);
if ( refs == null )
{
foundTypes[i] = ValueUtils.getValueType(argTypes[i]);
if ( foundTypes[i] == ValueType.ignore )
{
logger.log(LogService.LOG_DEBUG,
"Constructor argument type {0} not supported by constructor injection: {1}", null,
i, argTypes[i] );
}
}
else
{
for(final ReferenceMetadata ref : refs)
{
final ValueType t = ValueUtils.getReferenceValueType(componentClass, ref, argTypes[i], null, logger);
if ( t != null )
{
foundTypes[i] = t;
foundRefs[i] = ref;
break;
}
}
if ( foundTypes[i] == null )
{
foundTypes[i] = ValueType.ignore;
}
else
{
if ( refs.size() > 1 )
{
logger.log(LogService.LOG_ERROR,
"Several references for constructor injection of parameter {0}. Only {1} will be used out of: {2}.", null,
i, foundRefs[i].getName(), getNames(refs) );
}
}
}
if ( foundTypes[i] == ValueType.ignore )
{
hasFailure = true;
break;
}
}
if ( !hasFailure )
{
found = check;
break;
}
}
else
{
found = (Constructor<S>) c;
break;
}
}
}
this.constructor = found;
this.constructorArgTypes = foundTypes;
this.constructorRefs = foundRefs;
// activation fields
if ( componentMetadata.getActivationFields() != null )
{
activationFieldTypes = new ValueType[componentMetadata.getActivationFields().size()];
activationFields = new Field[activationFieldTypes.length];
int index = 0;
for(final String fieldName : componentMetadata.getActivationFields() )
{
final FieldUtils.FieldSearchResult result = FieldUtils.searchField(componentClass, fieldName, logger);
if ( result == null || result.field == null )
{
activationFieldTypes[index] = null;
activationFields[index] = null;
}
else
{
if ( result.usable )
{
activationFieldTypes[index] = ValueUtils.getValueType(result.field.getType());
activationFields[index] = result.field;
}
else
{
activationFieldTypes[index] = ValueType.ignore;
activationFields[index] = null;
}
}
index++;
}
}
else
{
activationFieldTypes = ValueUtils.EMPTY_VALUE_TYPES;
activationFields = null;
}
if ( constructor == null )
{
logger.log(LogService.LOG_ERROR,
"Constructor with {0} arguments not found. Component will fail.", null,
componentMetadata.getNumberOfConstructorParameters() );
}
else
{
logger.log(LogService.LOG_DEBUG,
"Found constructor with {0} arguments : {1}", null,
componentMetadata.getNumberOfConstructorParameters(), found );
}
}
/**
* Create a new instance
* @param componentContext The component context
* @param parameterMap A map of reference parameters for handling references in the
* constructor
* @return The instance
* @throws Exception If anything goes wrong, like constructor can't be found etc.
*/
@Override
public <T> S newInstance(final ScrComponentContext componentContext,
final Map<ReferenceMetadata, OpenStatus<S, ?>> parameterMap)
throws Exception
{
// no constructor -> throw
if ( constructor == null )
{
throw new InstantiationException("Constructor not found.");
}
final Object[] args;
if ( constructorArgTypes == null )
{
args = null;
}
else
{
args = new Object[constructorArgTypes.length];
for(int i=0; i<args.length; i++)
{
final ReferenceMetadata refMetadata = this.constructorRefs[i];
final OpenStatus<S, ?> status = refMetadata == null ? null : parameterMap.get(refMetadata);
if ( refMetadata == null )
{
args[i] = ValueUtils.getValue(constructor.getDeclaringClass().getName(),
constructorArgTypes[i],
constructor.getParameterTypes()[i],
componentContext,
null);
}
else
{
final List<Object> refs = refMetadata.isMultiple() ? new ArrayList<>() : null;
Object ref = null;
for(final RefPair<S, ?> refPair : status.getRefs(new AtomicInteger()))
{
if ( !refPair.isDeleted() && !refPair.isFailed() )
{
if ( refPair.getServiceObject(componentContext) == null
&& (constructorArgTypes[i] == ValueType.ref_serviceType
|| constructorArgTypes[i] == ValueType.ref_tuple
|| constructorArgTypes[i] == ValueType.ref_logger
|| constructorArgTypes[i] == ValueType.ref_formatterLogger) )
{
refPair.getServiceObject(componentContext, componentContext.getBundleContext());
}
ref = ValueUtils.getValue(constructor.getDeclaringClass().getName(),
constructorArgTypes[i],
constructor.getParameterTypes()[i],
componentContext,
refPair);
if ( refMetadata.isMultiple() && ref != null )
{
refs.add(ref);
}
}
}
if ( !refMetadata.isMultiple())
{
if ( ref == null )
{
throw new InstantiationException("Unable to get service for reference " + refMetadata.getName());
}
args[i] = ref;
}
else
{
args[i] = refs;
}
}
}
}
final S component = constructor.newInstance(args);
// activation fields
for(int i = 0; i<activationFieldTypes.length; i++)
{
if ( activationFieldTypes[i] != null && activationFieldTypes[i] != ValueType.ignore )
{
final Object value = ValueUtils.getValue(constructor.getDeclaringClass().getName(),
activationFieldTypes[i],
activationFields[i].getType(),
componentContext,
null); // null is ok as activation fields are not references
FieldUtils.setField(activationFields[i], component, value, componentContext.getLogger());
}
}
return component;
}
private String getNames(final List<ReferenceMetadata> refs)
{
final StringBuilder sb = new StringBuilder();
for(final ReferenceMetadata refMetadata : refs)
{
if ( sb.length() == 0 )
{
sb.append(refMetadata.getName());
}
else
{
sb.append(", ").append(refMetadata.getName());
}
}
return sb.toString();
}
}