| /** |
| * Licensed 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.aries.cdi.container.internal.model; |
| |
| import static org.apache.aries.cdi.container.internal.util.Filters.*; |
| import static org.apache.aries.cdi.container.internal.util.Reflection.*; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.WildcardType; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import javax.enterprise.inject.Instance; |
| import javax.enterprise.inject.spi.Annotated; |
| import javax.enterprise.inject.spi.AnnotatedCallable; |
| import javax.enterprise.inject.spi.AnnotatedConstructor; |
| import javax.enterprise.inject.spi.AnnotatedField; |
| import javax.enterprise.inject.spi.AnnotatedMethod; |
| import javax.enterprise.inject.spi.AnnotatedParameter; |
| import javax.enterprise.inject.spi.InjectionPoint; |
| import javax.inject.Named; |
| import javax.inject.Provider; |
| import javax.inject.Qualifier; |
| |
| import org.apache.aries.cdi.container.internal.util.Conversions; |
| import org.apache.aries.cdi.container.internal.util.Maps; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.service.cdi.MaximumCardinality; |
| import org.osgi.service.cdi.ReferencePolicy; |
| import org.osgi.service.cdi.ReferencePolicyOption; |
| import org.osgi.service.cdi.annotations.BeanPropertyType; |
| import org.osgi.service.cdi.annotations.PrototypeRequired; |
| import org.osgi.service.cdi.annotations.Reference; |
| import org.osgi.service.cdi.annotations.Reluctant; |
| import org.osgi.service.cdi.reference.BeanServiceObjects; |
| import org.osgi.service.cdi.reference.BindBeanServiceObjects; |
| import org.osgi.service.cdi.reference.BindService; |
| import org.osgi.service.cdi.reference.BindServiceReference; |
| import org.osgi.util.converter.TypeReference; |
| |
| public class ReferenceModel { |
| |
| public static class Builder { |
| |
| public Builder() {} |
| |
| public Builder(AnnotatedField<?> annotated) { |
| _annotated = annotated; |
| _declaringClass = annotated.getDeclaringType().getJavaClass(); |
| } |
| |
| public Builder(AnnotatedMethod<?> annotated) { |
| _annotated = annotated; |
| _declaringClass = annotated.getDeclaringType().getJavaClass(); |
| } |
| |
| public Builder(AnnotatedParameter<?> annotated) { |
| _annotated = annotated; |
| _declaringClass = annotated.getDeclaringCallable().getDeclaringType().getJavaClass(); |
| } |
| |
| public ReferenceModel build() { |
| Objects.requireNonNull(_annotated); |
| Objects.requireNonNull(_declaringClass); |
| Objects.requireNonNull(_type); |
| return new ReferenceModel(_type, _declaringClass, _annotated); |
| } |
| |
| public Builder injectionPoint(InjectionPoint injectionPoint) { |
| _annotated = injectionPoint.getAnnotated(); |
| _type = injectionPoint.getType(); |
| |
| if (_annotated instanceof AnnotatedParameter) { |
| AnnotatedParameter<?> parameter = (AnnotatedParameter<?>)_annotated; |
| |
| _declaringClass = parameter.getDeclaringCallable().getDeclaringType().getJavaClass(); |
| } |
| else if (_annotated instanceof AnnotatedField) { |
| AnnotatedField<?> field = (AnnotatedField<?>)_annotated; |
| |
| _declaringClass = field.getDeclaringType().getJavaClass(); |
| } |
| |
| return this; |
| } |
| |
| public Builder type(Type type) { |
| _type = type; |
| return this; |
| } |
| |
| private Annotated _annotated; |
| private Class<?> _declaringClass; |
| private Type _type; |
| |
| } |
| |
| public static enum Scope { |
| BUNDLE, PROTOTYPE, SINGLETON |
| } |
| |
| private static final TypeReference<Map<String, String>> _mapType = new TypeReference<Map<String, String>>(){}; |
| |
| private ReferenceModel( |
| Type injectionPointType, |
| Class<?> declaringClass, |
| Annotated annotated) { |
| |
| _annotated = annotated; |
| _injectionPointType = injectionPointType; |
| _declaringClass = declaringClass; |
| |
| _reference = _annotated.getAnnotation(Reference.class); |
| |
| _referenceType = getReferenceType(); |
| _referenceTarget = getReferenceTarget(); |
| _prototype = getQualifiers().stream().filter( |
| ann -> ann.annotationType().equals(PrototypeRequired.class) |
| ).findFirst().isPresent(); |
| |
| calculateServiceType(_injectionPointType); |
| |
| _referenceType.ifPresent(c -> { |
| if ((_serviceType != null) && !_serviceType.isAssignableFrom(c)) { |
| throw new IllegalArgumentException( |
| "The service type specified in @Reference (" + c + |
| ") is not compatible with the type calculated from the injection point: " + |
| _serviceType + " on " + _annotated); |
| } |
| |
| _serviceType = c; |
| }); |
| |
| Type rawType = _injectionPointType; |
| |
| if (rawType instanceof ParameterizedType) { |
| ParameterizedType pt = cast(_injectionPointType); |
| |
| rawType = pt.getRawType(); |
| } |
| |
| _beanClass = cast(rawType); |
| |
| _name = calculateName(_serviceType, _annotated); |
| |
| if (_annotated.isAnnotationPresent(Reluctant.class)) { |
| _greedy = false; |
| } |
| |
| _targetFilter = buildFilter(); |
| } |
| |
| private Optional<String> getReferenceTarget() { |
| if ((_reference != null) && (_reference.target().length() > 0)) { |
| return Optional.of(_reference.target()); |
| } |
| return Optional.empty(); |
| } |
| |
| private Optional<Class<?>> getReferenceType() { |
| if ((_reference != null) && (_reference.value() != null) && (_reference.value() != Object.class)) { |
| return Optional.of(_reference.value()); |
| } |
| return Optional.empty(); |
| } |
| |
| public Annotated getAnnotated() { |
| return _annotated; |
| } |
| |
| public Class<?> getBeanClass() { |
| return _beanClass; |
| } |
| |
| public CollectionType getCollectionType() { |
| return _collectionType; |
| } |
| |
| public Type getInjectionPointType() { |
| return _injectionPointType; |
| } |
| |
| public String getName() { |
| return _name; |
| } |
| |
| public Set<Annotation> getQualifiers() { |
| return _annotated.getAnnotations().stream().filter( |
| ann -> ann.annotationType().isAnnotationPresent(Qualifier.class) |
| ).collect(Collectors.toSet()); |
| } |
| |
| public Class<?> getServiceType() { |
| return _serviceType; |
| } |
| |
| public String getTarget() { |
| return _targetFilter; |
| } |
| |
| public boolean dynamic() { |
| return _dynamic; |
| } |
| |
| public boolean optional() { |
| return _optional; |
| } |
| |
| public ExtendedReferenceTemplateDTO toDTO() { |
| ExtendedReferenceTemplateDTO dto = new ExtendedReferenceTemplateDTO(); |
| dto.beanClass = _beanClass; |
| dto.collectionType = _collectionType; |
| dto.declaringClass = _declaringClass; |
| dto.injectionPointType = _injectionPointType; |
| dto.maximumCardinality = _multiplicity; |
| dto.minimumCardinality = (_multiplicity == MaximumCardinality.ONE) ? (_optional?0:1) : (0); |
| dto.name = _name; |
| dto.policy = (_dynamic) ? ReferencePolicy.DYNAMIC : ReferencePolicy.STATIC; |
| dto.policyOption = (_greedy) ? ReferencePolicyOption.GREEDY: ReferencePolicyOption.RELUCTANT; |
| dto.serviceClass = _serviceType; |
| dto.serviceType = _serviceType.getName(); |
| dto.targetFilter = _targetFilter; |
| |
| return dto; |
| } |
| |
| @Override |
| public String toString() { |
| if (_string == null) { |
| _string = toDTO().toString(); |
| } |
| return _string; |
| } |
| |
| public boolean unary() { |
| return _multiplicity == MaximumCardinality.ONE; |
| } |
| |
| private String buildFilter() { |
| String targetFilter = _referenceTarget.orElse(_emptyFilter); |
| boolean filterValid = false; |
| |
| int targetFilterLength = targetFilter.length(); |
| |
| if ((targetFilterLength > 0) && isValid(targetFilter)) { |
| filterValid = true; |
| } |
| |
| List<Annotation> beanPropertyTypes = _annotated.getAnnotations().stream().filter( |
| ann -> ann.annotationType().getAnnotation(BeanPropertyType.class) != null |
| ).collect(Collectors.toList()); |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| if (_prototype && filterValid || !beanPropertyTypes.isEmpty()) { |
| sb.append("(&"); |
| } |
| |
| beanPropertyTypes.forEach( |
| ann -> { |
| Map<String, String> map = Conversions.convert(ann).to(_mapType); |
| Maps.appendFilter(sb, map); |
| } |
| ); |
| |
| if (_prototype) { |
| sb.append("("); |
| sb.append(Constants.SERVICE_SCOPE); |
| sb.append("="); |
| sb.append(Constants.SCOPE_PROTOTYPE); |
| sb.append(")"); |
| } |
| |
| if (filterValid) { |
| sb.append(targetFilter); |
| } |
| |
| if (_prototype && filterValid || !beanPropertyTypes.isEmpty()) { |
| sb.append(")"); |
| } |
| |
| return sb.toString(); |
| } |
| |
| private String calculateName(Class<?> service, Annotated annotated) { |
| Named named = annotated.getAnnotation(Named.class); |
| |
| if (named != null) { |
| if (named.value() == null | named.value().equals("")) { |
| throw new IllegalArgumentException( |
| "It's illegal to specify @Name without specifying a value with @Reference: " + |
| annotated + " on " + _annotated); |
| } |
| return named.value(); |
| } |
| |
| String prefix = _declaringClass.getName() + "."; |
| |
| if (annotated instanceof AnnotatedParameter) { |
| AnnotatedParameter<?> parameter = (AnnotatedParameter<?>)annotated; |
| |
| AnnotatedCallable<?> declaringCallable = parameter.getDeclaringCallable(); |
| |
| if (declaringCallable instanceof AnnotatedConstructor) { |
| return prefix + "new" + parameter.getPosition(); |
| } |
| else { |
| AnnotatedMethod<?> method = (AnnotatedMethod<?>)declaringCallable; |
| |
| return prefix + method.getJavaMember().getName() + parameter.getPosition(); |
| } |
| } |
| else if (annotated instanceof AnnotatedMethod) { |
| AnnotatedMethod<?> method = (AnnotatedMethod<?>)annotated; |
| |
| return prefix + method.getJavaMember().getName(); |
| } |
| else { |
| AnnotatedField<?> annotatedField = (AnnotatedField<?>)annotated; |
| |
| return prefix + annotatedField.getJavaMember().getName(); |
| } |
| } |
| |
| private void calculateServiceType(Type type) { |
| if (!(type instanceof ParameterizedType)) { |
| if (!(type instanceof Class)) { |
| throw new IllegalArgumentException( |
| "The service type must not be generic: " + type + " on " + _annotated); |
| } |
| |
| Class<?> clazz = cast(type); |
| |
| if (Map.class == clazz) { |
| throw new IllegalArgumentException( |
| "Map must specify a generic type arguments: " + clazz); |
| } |
| else if (Map.Entry.class == clazz) { |
| throw new IllegalArgumentException( |
| "Map.Entry must specify a generic type arguments: " + clazz); |
| } |
| else if ((BeanServiceObjects.class == clazz) && !_referenceType.isPresent()) { |
| throw new IllegalArgumentException( |
| "ReferenceServiceObjects must specify a generic type argument: " + clazz); |
| } |
| else if ((ServiceReference.class == clazz) && !_referenceType.isPresent()) { |
| throw new IllegalArgumentException( |
| "ServiceReference must specify a generic type argument: " + type); |
| } |
| else if ((Collection.class == clazz || List.class == clazz) && |
| !_referenceType.isPresent()) { |
| |
| throw new IllegalArgumentException( |
| type + " must specify a generic type argument"); |
| } |
| else if (BeanServiceObjects.class == clazz) { |
| _collectionType = CollectionType.SERVICEOBJECTS; |
| return; |
| } |
| else if (ServiceReference.class == clazz) { |
| _collectionType = CollectionType.REFERENCE; |
| return; |
| } |
| else if (Collection.class == clazz || List.class == clazz) { |
| _collectionType = CollectionType.SERVICE; |
| _multiplicity = MaximumCardinality.MANY; |
| _optional = true; |
| return; |
| } |
| |
| _serviceType = clazz; |
| |
| return; |
| } |
| |
| ParameterizedType parameterizedType = cast(type); |
| |
| Type rawType = parameterizedType.getRawType(); |
| |
| Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); |
| |
| Type argument = actualTypeArguments[0]; |
| |
| if (Instance.class == cast(rawType)) { |
| throw new IllegalArgumentException( |
| "Instance<T> is not supported with @Reference: " + type); |
| } |
| |
| if (BindService.class.isAssignableFrom(cast(rawType))) { |
| _collectionType = CollectionType.BINDER_SERVICE; |
| _dynamic = true; |
| _multiplicity = MaximumCardinality.MANY; |
| _optional = true; |
| |
| if (argument instanceof WildcardType || |
| argument instanceof ParameterizedType) { |
| |
| throw new IllegalArgumentException( |
| "Type argument <S> of BindObject must not be generic: " + argument); |
| } |
| |
| _serviceType = cast(argument); |
| |
| return; |
| } |
| |
| if (BindServiceReference.class.isAssignableFrom(cast(rawType))) { |
| _collectionType = CollectionType.BINDER_REFERENCE; |
| _dynamic = true; |
| _multiplicity = MaximumCardinality.MANY; |
| _optional = true; |
| |
| if (argument instanceof WildcardType || |
| argument instanceof ParameterizedType) { |
| |
| throw new IllegalArgumentException( |
| "Type argument <S> of BindServiceReference must not be generic: " + argument); |
| } |
| |
| _serviceType = cast(argument); |
| |
| return; |
| } |
| |
| if (BindBeanServiceObjects.class.isAssignableFrom(cast(rawType))) { |
| _collectionType = CollectionType.BINDER_BEAN_SERVICE_OBJECTS; |
| _dynamic = true; |
| _multiplicity = MaximumCardinality.MANY; |
| _optional = true; |
| |
| if (argument instanceof WildcardType || |
| argument instanceof ParameterizedType) { |
| |
| throw new IllegalArgumentException( |
| "Type argument <S> of BindServiceObjects must not be generic: " + argument); |
| } |
| |
| _serviceType = cast(argument); |
| |
| return; |
| } |
| |
| if ((!_dynamic) && (Provider.class == cast(rawType))) { |
| _dynamic = true; |
| |
| calculateServiceType(argument); |
| |
| return; |
| } |
| |
| if ((!_optional) && (Optional.class == cast(rawType))) { |
| _optional = true; |
| |
| if ((argument instanceof WildcardType) && _referenceType.isPresent()) { |
| return; |
| } |
| |
| calculateServiceType(argument); |
| |
| return; |
| } |
| |
| if ((_multiplicity == MaximumCardinality.ONE) && |
| ((Collection.class == cast(rawType)) || |
| (List.class == cast(rawType)))) { |
| |
| _optional = true; |
| _multiplicity = MaximumCardinality.MANY; |
| |
| if ((argument instanceof WildcardType) && _referenceType.isPresent()) { |
| return; |
| } |
| |
| calculateServiceType(argument); |
| |
| return; |
| } |
| |
| if (Map.class == cast(rawType)) { |
| if (String.class != cast(argument)) { |
| throw new IllegalArgumentException( |
| "Maps of properties must use the form Map<String, (? | Object)>: " + type); |
| } |
| |
| argument = actualTypeArguments[1]; |
| |
| if ((Object.class != cast(argument)) && |
| (!(argument instanceof WildcardType))) { |
| |
| throw new IllegalArgumentException( |
| "Maps of properties must use the form Map<String, (? | Object)>: " + type); |
| } |
| |
| _collectionType = CollectionType.PROPERTIES; |
| |
| if (!_referenceType.isPresent()) { |
| throw new IllegalArgumentException( |
| "Maps of properties must specify service type with @Reference.value(): " + argument + " on " + _annotated); |
| } |
| |
| return; |
| } |
| |
| if (Map.Entry.class == cast(rawType)) { |
| if (!checkKey(argument)) { |
| throw new IllegalArgumentException( |
| "Tuples must have a key of type Map<String, (? | Object)>: " + argument + " on " + _annotated); |
| } |
| |
| _collectionType = CollectionType.TUPLE; |
| |
| Type second = actualTypeArguments[1]; |
| |
| if ((second instanceof WildcardType) && _referenceType.isPresent()) { |
| return; |
| } |
| |
| if (!(second instanceof Class)) { |
| throw new IllegalArgumentException( |
| "The service type must not be generic: " + second + " on " + _annotated); |
| } |
| |
| _serviceType = cast(second); |
| |
| return; |
| } |
| |
| if (BeanServiceObjects.class == cast(rawType)) { |
| _collectionType = CollectionType.SERVICEOBJECTS; |
| |
| if ((argument instanceof WildcardType) && _referenceType.isPresent()) { |
| return; |
| } |
| |
| calculateServiceType(argument); |
| |
| return; |
| } |
| |
| if (ServiceReference.class == cast(rawType)) { |
| _collectionType = CollectionType.REFERENCE; |
| |
| if ((argument instanceof WildcardType) && _referenceType.isPresent()) { |
| return; |
| } |
| |
| calculateServiceType(argument); |
| |
| return; |
| } |
| |
| _serviceType = cast(rawType); |
| |
| if (_serviceType.getTypeParameters().length > 0) { |
| throw new IllegalArgumentException( |
| "Illegal service type: " + argument + " on " + _annotated); |
| } |
| } |
| |
| // check the key type to make sure it complies with Map<String, ?> OR Map<String, Object> |
| private static boolean checkKey(Type mapEntryType) { |
| if (!(mapEntryType instanceof ParameterizedType)) { |
| return false; |
| } |
| |
| ParameterizedType parameterizedKeyType = (ParameterizedType)mapEntryType; |
| |
| if ((!Map.class.isAssignableFrom(cast(parameterizedKeyType.getRawType()))) || |
| (!parameterizedKeyType.getActualTypeArguments()[0].equals(String.class))) { |
| |
| return false; |
| } |
| |
| Type valueType = parameterizedKeyType.getActualTypeArguments()[1]; |
| |
| if ((!valueType.equals(Object.class) && |
| ( |
| (!(valueType instanceof WildcardType)) || |
| (((WildcardType)valueType).getUpperBounds().length != 1) || |
| (!((WildcardType)valueType).getUpperBounds()[0].equals(Object.class))))) { |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| private static final String _emptyFilter = ""; |
| |
| private final Annotated _annotated; |
| private Class<?> _beanClass; |
| private CollectionType _collectionType = CollectionType.SERVICE; |
| private final Class<?> _declaringClass; |
| private boolean _dynamic = false; |
| private boolean _greedy = true; |
| private final Type _injectionPointType; |
| private MaximumCardinality _multiplicity = MaximumCardinality.ONE; |
| private final String _name; |
| private boolean _optional = false; |
| private final boolean _prototype; |
| private final Reference _reference; |
| private final Optional<Class<?>> _referenceType; |
| private final Optional<String> _referenceTarget; |
| private Class<?> _serviceType; |
| private String _string; |
| private final String _targetFilter; |
| } |