blob: 0424ae1e2d280841dbde1a586e25c5a733198c72 [file] [log] [blame]
/**
* 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.container;
import static java.util.Arrays.asList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterDeploymentValidation;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;
import org.apache.aries.cdi.extension.spi.adapt.FiltersOn;
import org.apache.aries.cdi.extension.spi.adapt.MergeServiceTypes;
import org.apache.aries.cdi.extension.spi.adapt.ProcessPotentialService;
import org.apache.aries.cdi.extension.spi.adapt.RegisterExtension;
import org.apache.aries.cdi.extension.spi.annotation.AdaptedService;
import org.osgi.service.cdi.annotations.Service;
public class ServiceAdapterExtension implements Extension {
private static final Predicate<AnnotatedType<?>> ANNOTATED_TYPE_TRUE_PREDICATE = at -> true;
private boolean started;
private Collection<Entry<Predicate<AnnotatedType<?>>, BiConsumer<BeanManager, ProcessAnnotatedType<?>>>> forwardingObservers = new ArrayList<>();
void capturePotentialServiceObservers(@Observes final RegisterExtension registerExtension) {
if (started) {
throw new IllegalStateException("Container already started");
}
// declarative mode
forwardingObservers.addAll(forMethods(registerExtension.getExtension().getClass())
.map(method -> new SimpleImmutableEntry<>(registerExtension.getExtension(), method))
.filter(method -> Stream.of(method.getValue().getParameters())
.anyMatch(p -> p.isAnnotationPresent(Observes.class) && p.getType() == ProcessPotentialService.class))
.map(e -> new SimpleImmutableEntry<>(toPredicate(e.getValue()), toConsumer(e.getValue(), e.getKey())))
.collect(toList()));
// functional mode
forwardingObservers.addAll(registerExtension.getBuilders().stream()
.map(builder -> new SimpleImmutableEntry<>(
toPredicate(builder.getAnnotations(), builder.getTypes()),
(BiConsumer<BeanManager, ProcessAnnotatedType<?>>) (bm, pat) ->
builder.getConsumer().accept(bm, new ProcessPotentialService(pat))))
.collect(toList()));
}
<T> void forwardToObservers(@Observes final ProcessAnnotatedType<T> pat, final BeanManager beanManager) {
AnnotatedType<T> annotatedType;
if (!forwardingObservers.isEmpty() && !(annotatedType = pat.getAnnotatedType()).isAnnotationPresent(Service.class)) {
// simulate that but avoid a tons of events for all possible combinations of qualifiers which would be too slow
// beanManager.fireEvent(new ProcessPotentialService(pat), createAllPotentialQualifiers(pat));
forwardingObservers.stream()
.filter(predicateWithObserver -> predicateWithObserver.getKey().test(annotatedType))
.map(Entry::getValue)
.forEach(observer -> observer.accept(beanManager, pat));
}
}
void mergeAdaptedServiceTypes(@Observes final MergeServiceTypes mergeServiceTypes) {
if (started) {
throw new IllegalStateException("Container already started");
}
final AdaptedService adaptedService = mergeServiceTypes.getProcessAnnotatedType().getAnnotatedType().getAnnotation(AdaptedService.class);
final AnnotatedTypeConfigurator<?> configurator = mergeServiceTypes.getProcessAnnotatedType().configureAnnotatedType();
final Class<?>[] services;
if (adaptedService != null) {
configurator.remove(a -> a.annotationType() == AdaptedService.class);
services = Stream.concat(
Stream.of(mergeServiceTypes.getTypes()),
Stream.of(adaptedService.value()))
.distinct()
.toArray(Class[]::new);
}
else {
services = mergeServiceTypes.getTypes();
}
configurator.add(AdaptedService.Literal.of(services));
}
void setStarted(@Observes final AfterDeploymentValidation afterDeploymentValidation) {
started = true;
}
// using reflection since we are too early in CDI lifecycle to use CDI model
private Predicate<AnnotatedType<?>> toPredicate(final Method method) {
return Stream.of(method.getParameters())
.filter(parameter -> parameter.isAnnotationPresent(Observes.class))
.findFirst()
.map(parameter -> parameter.getAnnotation(FiltersOn.class))
.map(filters -> toPredicate(asList(filters.annotations()), asList(filters.types())))
.orElse(ANNOTATED_TYPE_TRUE_PREDICATE);
}
private Predicate<AnnotatedType<?>> toPredicate(final Collection<Class<?>> annotations, final Collection<Class<?>> types) {
return filterWithAnnotations(annotations).and(filterWithTypes(types));
}
private BiConsumer<BeanManager, ProcessAnnotatedType<?>> toConsumer(final Method method, final Extension instance) {
@SuppressWarnings("unchecked")
final BiFunction<BeanManager, ProcessAnnotatedType<?>, Object>[] argsFactory = Stream.of(method.getParameters())
.map(parameter -> lookupMethod(method, parameter))
.toArray(BiFunction[]::new);
if (!method.isAccessible()) {
method.setAccessible(true);
}
return (bm, pat) -> {
try {
method.invoke(
instance,
Stream.of(argsFactory).map(fn -> fn.apply(bm, pat)).toArray(Object[]::new));
}
catch (final IllegalAccessException e) {
throw new IllegalStateException(e);
}
catch (final InvocationTargetException e) {
throw new IllegalStateException(e.getTargetException());
}
};
}
private BiFunction<BeanManager, ProcessAnnotatedType<?>, Object> lookupMethod(
final Method method, final Parameter parameter) {
if (BeanManager.class == parameter.getType()) {
return (bm, pat) -> bm;
}
if (ProcessPotentialService.class == parameter.getType()) {
return (bm, pat) -> new ProcessPotentialService(pat);
}
throw new IllegalArgumentException(
"Unsupported type: " + parameter.getType() + " on " + method);
}
private Predicate<AnnotatedType<?>> filterWithTypes(final Collection<Class<?>> filterOnTypes) {
return Optional.of(filterOnTypes)
.filter(this::shouldNotIgnore)
.map(types -> (Predicate<AnnotatedType<?>>) annotatedType -> types.stream()
.anyMatch(t -> annotatedType.getJavaClass().isAssignableFrom(t)))
.orElse(ANNOTATED_TYPE_TRUE_PREDICATE);
}
private Predicate<AnnotatedType<?>> filterWithAnnotations(final Collection<Class<?>> filterOnAnnotations) {
return Optional.of(filterOnAnnotations)
.filter(this::shouldNotIgnore)
.map(withAnnotations -> (Predicate<AnnotatedType<?>>) annotatedType -> Stream.of(
Stream.of(annotatedType.getAnnotations()), // class
annotatedType.getFields().stream().map(Annotated::getAnnotations), // fields
Stream.concat( // (constructors + methods) x (self + parameters)
(Stream<? extends AnnotatedCallable<?>>)annotatedType.getMethods().stream(),
(Stream<? extends AnnotatedCallable<?>>)annotatedType.getConstructors().stream()
).flatMap(m -> Stream.concat(
Stream.of(m.getAnnotations()),
m.getParameters().stream().map(Annotated::getAnnotations))
)
).flatMap(identity()).anyMatch(
annotations -> annotations.stream().anyMatch(annotation ->
withAnnotations.stream().anyMatch(withAnnotation -> Stream.concat(
Stream.of(annotation.annotationType()),
Stream.of(annotation.annotationType().getAnnotations()).map(Annotation::annotationType))
.anyMatch(withAnnotation::isAssignableFrom))
)
)
).orElse(ANNOTATED_TYPE_TRUE_PREDICATE);
}
private boolean shouldNotIgnore(final Collection<Class<?>> annotations) {
return annotations.size() != 1 || FiltersOn.class != annotations.iterator().next();
}
private Stream<Method> forMethods(final Class<?> clazz) {
return clazz == Object.class || clazz == null ?
Stream.empty() :
Stream.concat(Stream.of(clazz.getDeclaredMethods()), forMethods(clazz.getSuperclass()));
}
}