/**
* 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.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import static java.lang.String.format;
import static java.util.Collections.addAll;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toSet;
import javax.enterprise.inject.CreationException;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.xml.bind.JAXBException;
import org.apache.camel.cdi.xml.ApplicationContextFactoryBean;
import org.apache.camel.cdi.xml.BeanManagerAware;
import org.apache.camel.cdi.xml.CamelContextFactoryBean;
import org.apache.camel.cdi.xml.ErrorHandlerDefinition;
import org.apache.camel.cdi.xml.ErrorHandlerType;
import org.apache.camel.cdi.xml.ImportDefinition;
import org.apache.camel.cdi.xml.RestContextDefinition;
import org.apache.camel.cdi.xml.RouteContextDefinition;
import org.apache.camel.core.xml.AbstractCamelFactoryBean;
import org.apache.camel.core.xml.CamelProxyFactoryDefinition;
import org.apache.camel.core.xml.CamelServiceExporterDefinition;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.model.IdentifiedType;
import org.apache.camel.model.OptionalIdentifiedDefinition;
import org.apache.camel.model.RouteDefinition;
import org.apache.camel.model.RoutesDefinition;
import org.apache.camel.model.rest.RestDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.cdi.AnyLiteral.ANY;
import static org.apache.camel.cdi.ApplicationScopedLiteral.APPLICATION_SCOPED;
import static org.apache.camel.cdi.DefaultLiteral.DEFAULT;
import static org.apache.camel.cdi.ResourceHelper.getResource;
import static org.apache.camel.cdi.Startup.Literal.STARTUP;
import static org.apache.camel.util.ObjectHelper.isEmpty;
import static org.apache.camel.util.ObjectHelper.isNotEmpty;
final class XmlCdiBeanFactory {
private final Logger logger = LoggerFactory.getLogger(XmlCdiBeanFactory.class);
private final BeanManager manager;
private final CdiCamelEnvironment environment;
private final CdiCamelExtension extension;
private XmlCdiBeanFactory(BeanManager manager, CdiCamelEnvironment environment, CdiCamelExtension extension) {
this.manager = manager;
this.environment = environment;
this.extension = extension;
}
static XmlCdiBeanFactory with(BeanManager manager, CdiCamelEnvironment environment, CdiCamelExtension extension) {
return new XmlCdiBeanFactory(manager, environment, extension);
}
Set<SyntheticBean<?>> beansFrom(String path) throws JAXBException, IOException {
URL url = getResource(path);
if (url == null) {
logger.warn("Unable to locate resource [{}] for import!", path);
return emptySet();
}
return beansFrom(url);
}
Set<SyntheticBean<?>> beansFrom(URL url) throws JAXBException, IOException {
try (InputStream xml = url.openStream()) {
Object node = XmlCdiJaxbContexts.CAMEL_CDI.instance()
.createUnmarshaller()
.unmarshal(xml);
if (node instanceof RoutesDefinition) {
RoutesDefinition routes = (RoutesDefinition) node;
return singleton(routesDefinitionBean(routes, url));
} else if (node instanceof ApplicationContextFactoryBean) {
ApplicationContextFactoryBean app = (ApplicationContextFactoryBean) node;
Set<SyntheticBean<?>> beans = new HashSet<>();
for (CamelContextFactoryBean factory : app.getContexts()) {
SyntheticBean<?> bean = camelContextBean(factory, url);
beans.add(bean);
beans.addAll(camelContextBeans(factory, bean, url));
}
for (ErrorHandlerDefinition definition : app.getErrorHandlers()) {
beans.add(errorHandlerBean(definition, url));
}
for (ImportDefinition definition : app.getImports()) {
// Get the base URL as imports are relative to this
String path = url.getFile().substring(0, url.getFile().lastIndexOf('/'));
String base = url.getProtocol() + "://" + url.getHost() + path;
beans.addAll(beansFrom(base + "/" + definition.getResource()));
}
for (RestContextDefinition factory : app.getRestContexts()) {
beans.add(restContextBean(factory, url));
}
for (RouteContextDefinition factory : app.getRouteContexts()) {
beans.add(routeContextBean(factory, url));
}
for (AbstractCamelFactoryBean<?> factory : app.getBeans()) {
if (hasId(factory)) {
beans.add(camelContextBean(null, factory, url));
}
}
return beans;
} else if (node instanceof CamelContextFactoryBean) {
CamelContextFactoryBean factory = (CamelContextFactoryBean) node;
Set<SyntheticBean<?>> beans = new HashSet<>();
SyntheticBean<?> bean = camelContextBean(factory, url);
beans.add(bean);
beans.addAll(camelContextBeans(factory, bean, url));
return beans;
} else if (node instanceof RestContextDefinition) {
RestContextDefinition factory = (RestContextDefinition) node;
return singleton(restContextBean(factory, url));
} else if (node instanceof RouteContextDefinition) {
RouteContextDefinition factory = (RouteContextDefinition) node;
return singleton(routeContextBean(factory, url));
}
}
return emptySet();
}
private SyntheticBean<?> camelContextBean(CamelContextFactoryBean factory, URL url) {
Set<Annotation> annotations = new HashSet<>();
annotations.add(ANY);
if (hasId(factory)) {
addAll(annotations,
ContextName.Literal.of(factory.getId()), NamedLiteral.of(factory.getId()));
} else {
annotations.add(DEFAULT);
factory.setImplicitId(true);
factory.setId(new CdiCamelContextNameStrategy().getNextName());
}
annotations.add(APPLICATION_SCOPED);
SyntheticAnnotated annotated = new SyntheticAnnotated(DefaultCamelContext.class,
manager.createAnnotatedType(DefaultCamelContext.class).getTypeClosure(),
annotations);
return new SyntheticBean<>(manager, annotated, DefaultCamelContext.class,
environment.camelContextInjectionTarget(
new SyntheticInjectionTarget<>(() -> {
DefaultCamelContext context = new DefaultCamelContext();
factory.setContext(context);
factory.setBeanManager(manager);
return context;
}, context -> {
try {
factory.afterPropertiesSet();
} catch (Exception cause) {
throw new CreationException(cause);
}
}),
annotated, manager, extension), bean ->
"imported Camel context with "
+ (factory.isImplicitId() ? "implicit " : "")
+ "id [" + factory.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers());
}
private Set<SyntheticBean<?>> camelContextBeans(CamelContextFactoryBean factory, Bean<?> context, URL url) {
Set<SyntheticBean<?>> beans = new HashSet<>();
// TODO: WARN log if the definition doesn't have an id
if (factory.getBeansFactory() != null) {
factory.getBeansFactory().stream()
.filter(XmlCdiBeanFactory::hasId)
.map(bean -> camelContextBean(context, bean, url))
.forEach(beans::add);
}
// TODO: define in beans
if (factory.getEndpoints() != null) {
factory.getEndpoints().stream()
.filter(XmlCdiBeanFactory::hasId)
.map(endpoint -> camelContextBean(context, endpoint, url))
.forEach(beans::add);
}
if (factory.getErrorHandlers() != null) {
factory.getErrorHandlers().stream()
.filter(XmlCdiBeanFactory::hasId)
.map(handler -> errorHandlerBean(handler, url))
.forEach(beans::add);
}
if (factory.getExports() != null) {
factory.getExports().stream()
.map(export -> serviceExporterBean(context, export, url))
.forEach(beans::add);
}
if (factory.getProxies() != null) {
factory.getProxies().stream()
.filter(XmlCdiBeanFactory::hasId)
.map(proxy -> proxyFactoryBean(context, proxy, url))
.forEach(beans::add);
}
// TODO: define in beans
if (factory.getRedeliveryPolicies() != null) {
factory.getRedeliveryPolicies().stream()
.filter(XmlCdiBeanFactory::hasId)
.map(policy -> camelContextBean(context, policy, url))
.forEach(beans::add);
}
return beans;
}
private SyntheticBean<?> camelContextBean(Bean<?> context, AbstractCamelFactoryBean<?> factory, URL url) {
if (factory instanceof BeanManagerAware) {
((BeanManagerAware) factory).setBeanManager(manager);
}
Set<Annotation> annotations = new HashSet<>();
annotations.add(ANY);
// FIXME: should add @ContextName if the Camel context bean has it
annotations.add(hasId(factory) ? NamedLiteral.of(factory.getId()) : DEFAULT);
// TODO: should that be @Singleton to enable injection points with bean instance type?
if (factory.isSingleton()) {
annotations.add(APPLICATION_SCOPED);
}
return new SyntheticBean<>(manager,
new SyntheticAnnotated(factory.getObjectType(),
manager.createAnnotatedType(factory.getObjectType()).getTypeClosure(),
annotations),
factory.getObjectType(),
new XmlFactoryBeanInjectionTarget<>(manager, factory, context), bean ->
"imported bean [" + factory.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers());
}
private SyntheticBean<?> proxyFactoryBean(Bean<?> context, CamelProxyFactoryDefinition proxy, URL url) {
if (isEmpty(proxy.getServiceRef()) && isEmpty(proxy.getServiceUrl())) {
throw new CreationException(
format("Missing [%s] or [%s] attribute for imported bean [%s] from resource [%s]",
"serviceRef", "serviceUrl", proxy.getId(), url));
}
return new XmlProxyFactoryBean<>(manager,
new SyntheticAnnotated(proxy.getServiceInterface(),
manager.createAnnotatedType(proxy.getServiceInterface()).getTypeClosure(),
APPLICATION_SCOPED, ANY, NamedLiteral.of(proxy.getId())),
proxy.getServiceInterface(), bean ->
"imported bean [" + proxy.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers(),
context, proxy);
}
private SyntheticBean<?> serviceExporterBean(Bean<?> context, CamelServiceExporterDefinition exporter, URL url) {
// TODO: replace with CreationException
requireNonNull(exporter.getServiceRef(),
() -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]",
"serviceRef", Objects.toString(exporter.getId(), "export"), url));
Class<?> type;
if (exporter.getServiceInterface() != null) {
type = exporter.getServiceInterface();
} else {
Bean<?> bean = manager.resolve(manager.getBeans(exporter.getServiceRef()));
if (bean != null) {
type = bean.getBeanClass();
} else {
requireNonNull(exporter.getServiceInterface(),
() -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]",
"serviceInterface", Objects.toString(exporter.getId(), "export"), url));
type = exporter.getServiceInterface();
}
}
Set<Type> types = new HashSet<>(manager.createAnnotatedType(type).getTypeClosure());
// Weld does not add Object.class for interfaces as they do not extend Object.class.
// Though let's add it so that it's possible to lookup by bean type Object.class
// beans whose bean class is an interface (for startup beans).
types.add(Object.class);
return new XmlServiceExporterBean<>(manager,
new SyntheticAnnotated(type, types, APPLICATION_SCOPED, ANY, STARTUP,
hasId(exporter) ? NamedLiteral.of(exporter.getId()) : DEFAULT),
type, bean ->
"imported bean [" + Objects.toString(exporter.getId(), "export") + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers(),
context, exporter);
}
private SyntheticBean<?> restContextBean(RestContextDefinition definition, URL url) {
requireNonNull(definition.getId(),
() -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]",
"id", "restContext", url));
return new SyntheticBean<>(manager,
new SyntheticAnnotated(List.class,
Stream.of(List.class, new ListParameterizedType(RestDefinition.class))
.collect(toSet()),
ANY, NamedLiteral.of(definition.getId())),
List.class,
new SyntheticInjectionTarget<>(definition::getRests), bean ->
"imported rest context with "
+ "id [" + definition.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers());
}
private SyntheticBean<?> routeContextBean(RouteContextDefinition definition, URL url) {
requireNonNull(definition.getId(),
() -> format("Missing [%s] attribute for imported bean [%s] from resource [%s]",
"id", "routeContext", url));
return new SyntheticBean<>(manager,
new SyntheticAnnotated(List.class,
Stream.of(List.class, new ListParameterizedType(RouteDefinition.class))
.collect(toSet()),
ANY, NamedLiteral.of(definition.getId())),
List.class,
new SyntheticInjectionTarget<>(definition::getRoutes), bean ->
"imported route context with "
+ "id [" + definition.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers());
}
private SyntheticBean<?> routesDefinitionBean(RoutesDefinition definition, URL url) {
return new SyntheticBean<>(manager,
// TODO: should be @Named if the id is set
new SyntheticAnnotated(RoutesDefinition.class,
manager.createAnnotatedType(RoutesDefinition.class).getTypeClosure(),
ANY, DEFAULT),
RoutesDefinition.class,
new SyntheticInjectionTarget<>(() -> definition), bean ->
"imported routes definition "
+ (hasId(definition) ? "[" + definition.getId() + "] " : "")
+ "from resource [" + url + "]");
}
private SyntheticBean<?> errorHandlerBean(ErrorHandlerDefinition definition, URL url) {
ErrorHandlerType type = definition.getType();
// Validate attributes according to type
if (isNotEmpty(definition.getDeadLetterUri())
&& !type.equals(ErrorHandlerType.DeadLetterChannel)) {
throw attributeNotSupported("deadLetterUri", type, definition.getId());
}
if (isNotEmpty(definition.getDeadLetterHandleNewException())
&& !type.equals(ErrorHandlerType.DeadLetterChannel)) {
throw attributeNotSupported("deadLetterHandleNewException", type, definition.getId());
}
if (isNotEmpty(definition.getTransactionTemplateRef())
&& !type.equals(ErrorHandlerType.TransactionErrorHandler)) {
throw attributeNotSupported("transactionTemplateRef", type, definition.getId());
}
if (isNotEmpty(definition.getTransactionManagerRef())
&& !type.equals(ErrorHandlerType.TransactionErrorHandler)) {
throw attributeNotSupported("transactionManagerRef", type, definition.getId());
}
if (isNotEmpty(definition.getRollbackLoggingLevel())
&& (!type.equals(ErrorHandlerType.TransactionErrorHandler))) {
throw attributeNotSupported("rollbackLoggingLevel", type, definition.getId());
}
if (isNotEmpty(definition.getUseOriginalMessage())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("useOriginalMessage", type, definition.getId());
}
if (isNotEmpty(definition.getOnRedeliveryRef())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("onRedeliveryRef", type, definition.getId());
}
if (isNotEmpty(definition.getOnExceptionOccurredRef())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("onExceptionOccurredRef", type, definition.getId());
}
if (isNotEmpty(definition.getOnPrepareFailureRef())
&& (type.equals(ErrorHandlerType.TransactionErrorHandler)
|| type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("onPrepareFailureRef", type, definition.getId());
}
if (isNotEmpty(definition.getRetryWhileRef())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("retryWhileRef", type, definition.getId());
}
if (isNotEmpty(definition.getOnRedeliveryRef())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("redeliveryPolicyRef", type, definition.getId());
}
if (isNotEmpty(definition.getExecutorServiceRef())
&& (type.equals(ErrorHandlerType.LoggingErrorHandler)
|| type.equals(ErrorHandlerType.NoErrorHandler))) {
throw attributeNotSupported("executorServiceRef", type, definition.getId());
}
if (isNotEmpty(definition.getLogName())
&& (!type.equals(ErrorHandlerType.LoggingErrorHandler))) {
throw attributeNotSupported("logName", type, definition.getId());
}
if (isNotEmpty(definition.getLevel())
&& (!type.equals(ErrorHandlerType.LoggingErrorHandler))) {
throw attributeNotSupported("level", type, definition.getId());
}
return new XmlErrorHandlerFactoryBean(manager,
new SyntheticAnnotated(type.getTypeAsClass(),
manager.createAnnotatedType(type.getTypeAsClass()).getTypeClosure(),
ANY, NamedLiteral.of(definition.getId())),
type.getTypeAsClass(), bean -> "imported error handler with "
+ "id [" + definition.getId() + "] "
+ "from resource [" + url + "] "
+ "with qualifiers " + bean.getQualifiers(),
definition);
}
private static CreationException attributeNotSupported(String attribute, ErrorHandlerType type, String id) {
return new CreationException(
format("Attribute [%s] is not supported by error handler type [%s], in error handler with id [%s]",
attribute, type, id));
}
private static <T extends IdentifiedType> boolean hasId(T type) {
return isNotEmpty(type.getId());
}
private static <T extends OptionalIdentifiedDefinition<T>> boolean hasId(T type) {
return isNotEmpty(type.getId());
}
}