blob: d82d693a9a0df41faaf8c30b7604d419a05a6463 [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.field;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.felix.scr.impl.inject.BindParameters;
import org.apache.felix.scr.impl.inject.InitReferenceMethod;
import org.apache.felix.scr.impl.inject.MethodResult;
import org.apache.felix.scr.impl.inject.RefPair;
import org.apache.felix.scr.impl.inject.ReferenceMethod;
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.FieldSearchResult;
import org.apache.felix.scr.impl.inject.internal.ClassUtils;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.metadata.ReferenceMetadata;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.LogService;
/**
* Handler for field references
*/
public class FieldHandler
{
/** The reference metadata. */
private final ReferenceMetadata metadata;
/** The component class. */
private final Class<?> componentClass;
/** The field used for the injection. */
private volatile Field field;
/** Value type. */
private volatile ValueType valueType;
/** State handling. */
private volatile State state;
/**
* Create a new field handler
* @param fieldName name of the field
* @param componentClass component class
* @param referenceClassName service class name
*/
public FieldHandler( final ReferenceMetadata metadata,
final Class<?> componentClass)
{
this.metadata = metadata;
this.componentClass = componentClass;
this.state = NotResolved.INSTANCE;
}
private enum METHOD_TYPE
{
BIND,
UNBIND,
UPDATED
}
private boolean initField(final Object componentInstance,
final ComponentLogger logger )
{
if ( valueType == ValueType.ignore )
{
return true;
}
try
{
if ( metadata.isMultiple() )
{
if ( metadata.isReplace() )
{
this.setFieldValue(componentInstance, new CopyOnWriteArrayList<>());
}
else
{
final Class<?> fieldType = this.field.getType();
// update strategy: if DS implementation provides collection implementation
// only list and collection are allowed, field must not be final
final Object providedImpl = this.getFieldValue(componentInstance);
if ( providedImpl == null)
{
if ( Modifier.isFinal(this.field.getModifiers()) )
{
logger.log( LogService.LOG_ERROR, "Field {0} in class {1} must not be declared as final", null,
metadata.getField(), this.componentClass );
valueType = ValueType.ignore;
return true;
}
if ( fieldType != ClassUtils.LIST_CLASS && fieldType != ClassUtils.COLLECTION_CLASS )
{
logger.log( LogService.LOG_ERROR, "Field {0} in class {1} has unsupported type {2}."+
" It must be one of java.util.Collection or java.util.List.", null,
metadata.getField(), this.componentClass, fieldType.getName() );
valueType = ValueType.ignore;
return true;
}
if ( fieldType == ClassUtils.LIST_CLASS )
{
this.setFieldValue(componentInstance, new CopyOnWriteArrayList<>());
}
else
{
this.setFieldValue(componentInstance, new CopyOnWriteArraySet<>());
}
}
}
}
else
{
// only optional field need initialization
if ( metadata.isOptional() )
{
// null the field if optional and unary
this.setFieldValue(componentInstance, null);
}
}
}
catch ( final InvocationTargetException ite)
{
valueType = ValueType.ignore;
logger.log( LogService.LOG_ERROR, "Field {0} in class {1} can't be initialized.",
ite, metadata.getField(), this.componentClass );
return false;
}
return true;
}
private Collection<Object> getReplaceCollection(final BindParameters bp)
{
final List<Object> objects = new ArrayList<>();
for(final Object val : bp.getComponentContext().getBoundValues(metadata.getName()).values())
{
objects.add(val);
}
return objects;
}
private MethodResult updateField(final METHOD_TYPE mType,
final Object componentInstance,
final BindParameters bp)
throws InvocationTargetException
{
final ScrComponentContext key = bp.getComponentContext();
final RefPair<?, ?> refPair = bp.getRefPair();
if ( !this.metadata.isMultiple() )
{
// unary references
// unbind needs only be done, if reference is dynamic and optional
if ( mType == METHOD_TYPE.UNBIND )
{
if ( this.metadata.isOptional() && !this.metadata.isStatic() )
{
// we only reset if it was previously set with this value
if ( bp.getComponentContext().getBoundValues(metadata.getName()).size() == 1 )
{
this.setFieldValue(componentInstance, null);
}
}
bp.getComponentContext().getBoundValues(metadata.getName()).remove(refPair);
}
// updated needs only be done, if the value type is map or tuple
// If it's a dynamic reference, the value can be updated
// for a static reference we need a reactivation
else if ( mType == METHOD_TYPE.UPDATED )
{
if ( this.valueType == ValueType.ref_map || this.valueType == ValueType.ref_tuple )
{
if ( this.metadata.isStatic() )
{
return MethodResult.REACTIVATE;
}
final Object obj = ValueUtils.getValue(componentInstance.getClass().getName(),
valueType, field.getType(), key, refPair);
this.setFieldValue(componentInstance, obj);
bp.getComponentContext().getBoundValues(metadata.getName()).put(refPair, obj);
}
}
// bind needs always be done
else
{
final Object obj = ValueUtils.getValue(componentInstance.getClass().getName(),
valueType, field.getType(), key, refPair);
this.setFieldValue(componentInstance, obj);
bp.getComponentContext().getBoundValues(metadata.getName()).put(refPair, obj);
}
}
else
{
// multiple references
// bind: replace or update the field
if ( mType == METHOD_TYPE.BIND )
{
final Object obj = ValueUtils.getValue(componentInstance.getClass().getName(),
valueType, field.getType(), key, refPair);
bp.getComponentContext().getBoundValues(metadata.getName()).put(refPair, obj);
if ( metadata.isReplace() )
{
this.setFieldValue(componentInstance, getReplaceCollection(bp));
}
else
{
@SuppressWarnings("unchecked")
final Collection<Object> col = (Collection<Object>)this.getFieldValue(componentInstance);
col.add(obj);
}
}
// unbind needs only be done, if reference is dynamic
else if ( mType == METHOD_TYPE.UNBIND)
{
if ( !metadata.isStatic() )
{
final Object obj = bp.getComponentContext().getBoundValues(metadata.getName()).remove(refPair);
if ( metadata.isReplace() )
{
this.setFieldValue(componentInstance, getReplaceCollection(bp));
}
else
{
@SuppressWarnings("unchecked")
final Collection<Object> col = (Collection<Object>)this.getFieldValue(componentInstance);
col.remove(obj);
}
}
}
// updated needs only be done, if the value type is map or tuple
else if ( mType == METHOD_TYPE.UPDATED)
{
if ( this.valueType == ValueType.ref_map || this.valueType == ValueType.ref_tuple )
{
if ( !this.metadata.isStatic() )
{
final Object obj = ValueUtils.getValue(componentInstance.getClass().getName(),
valueType, field.getType(), key, refPair);
final Object oldObj = bp.getComponentContext().getBoundValues(metadata.getName()).put(refPair, obj);
if ( metadata.isReplace() )
{
this.setFieldValue(componentInstance, getReplaceCollection(bp));
}
else
{
@SuppressWarnings("unchecked")
final Collection<Object> col = (Collection<Object>)this.getFieldValue(componentInstance);
col.add(obj);
col.remove(oldObj);
}
}
else
{
// if it's static we need to reactivate
return MethodResult.REACTIVATE;
}
}
}
}
return MethodResult.VOID;
}
private void setFieldValue(final Object componentInstance, final Object value)
throws InvocationTargetException
{
try
{
field.set(componentInstance, value);
}
catch ( final IllegalArgumentException iae )
{
throw new InvocationTargetException(iae);
}
catch ( final IllegalAccessException iae )
{
throw new InvocationTargetException(iae);
}
}
private Object getFieldValue(final Object componentInstance)
throws InvocationTargetException
{
try
{
return field.get(componentInstance);
}
catch ( final IllegalArgumentException iae )
{
throw new InvocationTargetException(iae);
}
catch ( final IllegalAccessException iae )
{
throw new InvocationTargetException(iae);
}
}
/**
* Internal state interface.
*/
private static interface State
{
MethodResult invoke( FieldHandler handler,
METHOD_TYPE mType,
Object componentInstance,
BindParameters rawParameter)
throws InvocationTargetException;
boolean fieldExists( FieldHandler handler, ComponentLogger logger);
}
/**
* Initial state.
*/
private static class NotResolved implements State
{
private static final State INSTANCE = new NotResolved();
private void resolve(final FieldHandler handler, final ComponentLogger logger)
{
logger.log( LogService.LOG_DEBUG, "getting field: {0}", null, handler.metadata.getField() );
// resolve the field
final FieldUtils.FieldSearchResult result = FieldUtils.searchField( handler.componentClass, handler.metadata.getField(), logger );
handler.setSearchResult(result, logger);
}
@Override
public MethodResult invoke( final FieldHandler handler,
final METHOD_TYPE mType,
final Object componentInstance,
final BindParameters rawParameter)
throws InvocationTargetException
{
resolve( handler, rawParameter.getComponentContext().getLogger() );
return handler.state.invoke( handler, mType, componentInstance, rawParameter );
}
@Override
public boolean fieldExists( final FieldHandler handler, final ComponentLogger logger)
{
resolve( handler, logger );
return handler.state.fieldExists( handler, logger );
}
}
/**
* Final state of field couldn't be found or errors occurred.
*/
private static class NotFound implements State
{
private static final State INSTANCE = new NotFound();
@Override
public MethodResult invoke( final FieldHandler handler,
final METHOD_TYPE mType,
final Object componentInstance,
final BindParameters rawParameter)
{
rawParameter.getComponentContext().getLogger().log( LogService.LOG_ERROR, "Field [{0}] not found", null,
handler.metadata.getField() );
return null;
}
@Override
public boolean fieldExists( final FieldHandler handler, final ComponentLogger logger)
{
return false;
}
}
/**
* Final state of field could be found and is valid.
*/
private static class Resolved implements State
{
private static final State INSTANCE = new Resolved();
@Override
public MethodResult invoke( final FieldHandler handler,
final METHOD_TYPE mType,
final Object componentInstance,
final BindParameters rawParameter)
throws InvocationTargetException
{
return handler.updateField( mType, componentInstance, rawParameter );
}
@Override
public boolean fieldExists( final FieldHandler handler, final ComponentLogger logger)
{
return true;
}
}
public boolean fieldExists( ComponentLogger logger )
{
return this.state.fieldExists( this, logger );
}
synchronized void setSearchResult(FieldSearchResult result, ComponentLogger logger)
{
if (result == null)
{
field = null;
valueType = null;
state = NotFound.INSTANCE;
// TODO - will component really fail?
logger.log(LogService.LOG_ERROR, "Field [{0}] not found; Component will fail",
null, metadata.getField());
}
else
{
field = result.field;
if (!result.usable)
{
valueType = ValueType.ignore;
}
else
{
valueType = ValueUtils.getReferenceValueType(componentClass, metadata,
result.field.getType(), result.field, logger);
}
state = Resolved.INSTANCE;
logger.log(LogService.LOG_DEBUG, "Found field: {0}",
null, result.field );
}
}
public static final class ReferenceMethodImpl
implements ReferenceMethod
{
private final METHOD_TYPE methodType;
private final FieldHandler handler;
public ReferenceMethodImpl(final METHOD_TYPE mt, final FieldHandler handler)
{
this.methodType = mt;
this.handler = handler;
}
@Override
public <S, T> MethodResult invoke(final Object componentInstance,
final BindParameters rawParameter,
final MethodResult methodCallFailureResult)
{
if ( handler.valueType == ValueType.ignore )
{
return MethodResult.VOID;
}
try
{
return handler.state.invoke( handler,
methodType,
componentInstance,
rawParameter);
}
catch ( final InvocationTargetException ite )
{
rawParameter.getComponentContext().getLogger().log( LogService.LOG_ERROR, "The {0} field has thrown an exception", null,
handler.metadata.getField() );
}
return methodCallFailureResult;
}
@Override
public <S, T> boolean getServiceObject(
final BindParameters rawParameter,
final BundleContext context)
{
if ( methodType != METHOD_TYPE.UNBIND )
{
//??? this resolves which we need.... better way?
if (rawParameter.getRefPair().getServiceObject(rawParameter.getComponentContext()) == null
&& handler.fieldExists( rawParameter.getComponentContext().getLogger() )
&& (handler.valueType == ValueType.ref_serviceType || handler.valueType == ValueType.ref_tuple
|| handler.valueType == ValueType.ref_logger || handler.valueType == ValueType.ref_formatterLogger) )
{
return rawParameter.getRefPair().getServiceObject(rawParameter.getComponentContext(), context);
}
}
return true;
}
}
public ReferenceMethod getBind()
{
return new ReferenceMethodImpl(METHOD_TYPE.BIND, this);
}
public ReferenceMethod getUnbind()
{
return new ReferenceMethodImpl(METHOD_TYPE.UNBIND, this);
}
public ReferenceMethod getUpdated()
{
return new ReferenceMethodImpl(METHOD_TYPE.UPDATED, this);
}
public InitReferenceMethod getInit()
{
if ( valueType == ValueType.ignore )
{
return null;
}
return new InitReferenceMethod()
{
@Override
public boolean init(final Object componentInstance, final ComponentLogger logger)
{
if ( fieldExists( logger ) )
{
return initField(componentInstance, logger);
}
return false;
}
};
}
}