| /* |
| * 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; |
| } |
| }; |
| } |
| } |