/**
* 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.camel.cdi;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.InjectionTarget;
import javax.enterprise.util.TypeLiteral;
import javax.inject.Inject;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.impl.DefaultEndpoint;
/**
* A Camel {@link Endpoint} that bridges the CDI events facility with Camel routes so that CDI events
* can be seamlessly observed / consumed (respectively produced / fired) from Camel consumers (respectively by Camel producers).<p>
*
* The {@code CdiEventEndpoint<T>} bean can be used to observe / consume CDI events whose event type is {@code T}, for example:
* <pre><code>
* {@literal @}Inject
* CdiEventEndpoint{@literal <}String{@literal >} cdiEventEndpoint;
*
* from(cdiEventEndpoint).log("CDI event received: ${body}");
* </code></pre>
*
* Conversely, the {@code CdiEventEndpoint<T>} bean can be used to produce / fire CDI events whose event type is {@code T}, for example:
* <pre><code>
* {@literal @}Inject
* CdiEventEndpoint{@literal <}String{@literal >} cdiEventEndpoint;
*
* from("direct:event").to(cdiEventEndpoint).log("CDI event sent: ${body}");
* </code></pre>
*
* The type variable {@code T}, respectively the qualifiers, of a particular {@code CdiEventEndpoint<T>} injection point
* are automatically translated into the parameterized <i>event type</i>, respectively into the <i>event qualifiers</i>, e.g.:
* <pre><code>
* {@literal @}Inject
* {@literal @}FooQualifier
* CdiEventEndpoint{@literal <}List{@literal <}String{@literal >}{@literal >} cdiEventEndpoint;
*
* from("direct:event").to(cdiEventEndpoint);
*
* void observeCdiEvents({@literal @}Observes {@literal @}FooQualifier List{@literal <}String{@literal >} event) {
* logger.info("CDI event: {}", event);
* }
* </code></pre>
*
* When multiple Camel contexts exist in the CDI container, the {@code @ContextName} qualifier can be used
* to qualify the {@code CdiEventEndpoint<T>} injection points, e.g.:
* <pre><code>
* {@literal @}Inject
* {@literal @}ContextName("foo")
* CdiEventEndpoint{@literal <}List{@literal <}String{@literal >}{@literal >} cdiEventEndpoint;
*
* // Only observe / consume events having the {@literal @}ContextName("foo") qualifier
* from(cdiEventEndpoint).log("Camel context 'foo'{@literal >} CDI event received: ${body}");
*
* // Produce / fire events with the {@literal @}ContextName("foo") qualifier
* from("...").to(cdiEventEndpoint);
*
* void observeCdiEvents({@literal @}Observes {@literal @}ContextName("foo") List{@literal <}String{@literal >} event) {
* logger.info("Camel context 'foo'{@literal >} CDI event: {}", event);
* }
* </code></pre>
*/
public final class CdiEventEndpoint<T> extends DefaultEndpoint {
private final List<CdiEventConsumer<T>> consumers = new ArrayList<>();
private final Type type;
private final Set<Annotation> qualifiers;
private final BeanManager manager;
CdiEventEndpoint(String endpointUri, Type type, Set<Annotation> qualifiers, BeanManager manager) {
super(endpointUri);
this.type = type;
this.qualifiers = qualifiers;
this.manager = manager;
}
static String eventEndpointUri(Type type, Set<Annotation> qualifiers) {
return "cdi-event://" + authorityFromType(type) + qualifiers.stream()
.map(CdiSpiHelper::createAnnotationId)
.collect(joining("%2C", qualifiers.size() > 0 ? "?qualifiers=" : "", ""));
}
private static String authorityFromType(Type type) {
if (type instanceof Class) {
return Class.class.cast(type).getName();
}
if (type instanceof ParameterizedType) {
return Stream.of(((ParameterizedType) type).getActualTypeArguments())
.map(CdiEventEndpoint::authorityFromType)
.collect(joining("%2C", authorityFromType(((ParameterizedType) type).getRawType()) + "%3C", "%3E"));
}
if (type instanceof GenericArrayType) {
return authorityFromType(((GenericArrayType) type).getGenericComponentType()) + "%5B%5D";
}
throw new IllegalArgumentException("Cannot create URI authority for event type [" + type + "]");
}
Set<Annotation> getQualifiers() {
return qualifiers;
}
Type getType() {
return type;
}
@Override
public Consumer createConsumer(Processor processor) {
return new CdiEventConsumer<>(this, processor);
}
@Override
public Producer createProducer() throws IllegalAccessException {
// FIXME: to be replaced once event firing with dynamic parameterized type
// is properly supported (see https://issues.jboss.org/browse/CDI-516)
TypeLiteral<T> literal = new TypeLiteral<T>() {
};
for (Field field : TypeLiteral.class.getDeclaredFields()) {
if (field.getType().equals(Type.class)) {
field.setAccessible(true);
field.set(literal, type);
break;
}
}
InjectionTarget<AnyEvent> target = manager.createInjectionTarget(manager.createAnnotatedType(AnyEvent.class));
CreationalContext<AnyEvent> ctx = manager.createCreationalContext(null);
AnyEvent instance = target.produce(ctx);
target.inject(instance, ctx);
return new CdiEventProducer<>(this, instance.event
.select(literal, qualifiers.toArray(new Annotation[0])));
}
@Vetoed
private static class AnyEvent {
@Any
@Inject
private Event<Object> event;
}
@Override
public boolean isSingleton() {
return true;
}
void addConsumer(CdiEventConsumer<T> consumer) {
synchronized (consumers) {
consumers.add(consumer);
}
}
void removeConsumer(CdiEventConsumer<T> consumer) {
synchronized (consumers) {
consumers.remove(consumer);
}
}
void notify(T t) {
synchronized (consumers) {
consumers.forEach(consumer -> consumer.notify(t));
}
}
}