
io.syndesis.integration.component.proxy.ComponentProxyComponent Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2016 Red Hat, Inc.
*
* 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 io.syndesis.integration.component.proxy;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.apache.camel.CamelContext;
import org.apache.camel.Component;
import org.apache.camel.Endpoint;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.Processor;
import org.apache.camel.TypeConversionException;
import org.apache.camel.TypeConverter;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.catalog.DefaultCamelCatalog;
import org.apache.camel.component.extension.ComponentExtension;
import org.apache.camel.component.extension.ComponentVerifierExtension;
import org.apache.camel.component.extension.verifier.ResultBuilder;
import org.apache.camel.component.extension.verifier.ResultErrorBuilder;
import org.apache.camel.impl.DefaultComponent;
import org.apache.camel.util.IntrospectionSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StringHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.function.Predicates;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("PMD.GodClass")
public class ComponentProxyComponent extends DefaultComponent {
private static final Logger LOGGER = LoggerFactory.getLogger(ComponentProxyComponent.class);
private final CamelCatalog catalog;
private final String componentId;
private final String componentScheme;
private final Map configuredOptions;
private final Map remainingOptions;
private final ComponentDefinition definition;
private Optional componentSchemeAlias;
private Processor beforeProducer;
private Processor afterProducer;
private Processor beforeConsumer;
private Processor afterConsumer;
public ComponentProxyComponent(String componentId, String componentScheme) {
this(componentId, componentScheme, (String)null, new DefaultCamelCatalog(false));
}
public ComponentProxyComponent(String componentId, String componentScheme, CamelCatalog catalog) {
this(componentId, componentScheme, (String)null, catalog);
}
public ComponentProxyComponent(String componentId, String componentScheme, String componentClass) {
this(componentId, componentScheme, componentClass, new DefaultCamelCatalog(false));
}
public ComponentProxyComponent(String componentId, String componentScheme, Class> componentClass) {
this(componentId, componentScheme, componentClass.getName(), new DefaultCamelCatalog(false));
}
public ComponentProxyComponent(String componentId, String componentScheme, Class> componentClass, CamelCatalog catalog) {
this(componentId, componentScheme, componentClass.getName(), catalog);
}
public ComponentProxyComponent(String componentId, String componentScheme, String componentClass, CamelCatalog catalog) {
this.componentId = StringHelper.notEmpty(componentId, "componentId");
this.componentScheme = StringHelper.notEmpty(componentScheme, "componentScheme");
this.componentSchemeAlias = Optional.empty();
this.configuredOptions = new HashMap<>();
this.remainingOptions = new HashMap<>();
this.catalog = ObjectHelper.notNull(catalog, "catalog");
if (ObjectHelper.isNotEmpty(componentClass)) {
this.catalog.addComponent(componentScheme, componentClass);
}
try {
this.definition = ComponentDefinition.forScheme(catalog, componentScheme);
} catch (IOException e) {
throw ObjectHelper.wrapRuntimeCamelException(e);
}
registerExtension(this::getComponentVerifierExtension);
}
public void setOptions(Map options) {
this.configuredOptions.clear();
if (ObjectHelper.isNotEmpty(options)) {
for (Map.Entry entry : options.entrySet()) {
// Filter out null values
if (entry.getValue() != null) {
this.configuredOptions.put(entry.getKey(), entry.getValue());
}
}
}
}
/**
* Allows the definition to be overridden if required by specific components
* @return definition
*/
protected ComponentDefinition getDefinition() {
return definition;
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map parameters) {
// merge parameters
final Map options = new HashMap<>();
doAddOptions(options, this.remainingOptions);
doAddOptions(options, parameters);
// create the uri of the base component, DO NOT log the computed delegate
final Map endpointOptions = buildEndpointOptions(remaining, options);
final String endpointScheme = componentSchemeAlias.orElse(componentScheme);
ComponentDefinition definition = getDefinition();
final Endpoint delegate = createDelegateEndpoint(definition, endpointScheme, endpointOptions);
LOGGER.info("Connector resolved: {} -> {}", URISupport.sanitizeUri(uri), URISupport.sanitizeUri(delegate.getEndpointUri()));
// remove options already set on the endpoint
options.keySet().removeIf(endpointOptions::containsKey);
// Configure the delegated endpoint
configureDelegateEndpoint(definition, delegate, options);
final ComponentProxyEndpoint answer = new ComponentProxyEndpoint(uri, this, delegate);
answer.setBeforeProducer(ObjectHelper.trySetCamelContext(getBeforeProducer(), getCamelContext()));
answer.setAfterProducer(ObjectHelper.trySetCamelContext(getAfterProducer(), getCamelContext()));
answer.setBeforeConsumer(ObjectHelper.trySetCamelContext(getBeforeConsumer(), getCamelContext()));
answer.setAfterConsumer(ObjectHelper.trySetCamelContext(getAfterConsumer(), getCamelContext()));
// clean-up parameters so that validation won't fail later on
// in DefaultConnectorComponent.validateParameters()
parameters.clear();
return answer;
}
@Override
protected void doStart() throws Exception {
this.remainingOptions.clear();
this.remainingOptions.putAll(this.configuredOptions);
enrichOptions(this.remainingOptions);
ComponentDefinition definition = getDefinition();
Optional component = createDelegateComponent(definition, this.remainingOptions);
if (component.isPresent()) {
// Configure the component, options should be removed once consumed
configureDelegateComponent(definition, component.get(), this.remainingOptions);
// Create a unique delegate component alias
componentSchemeAlias = Optional.of(componentScheme + "-" + componentId);
if (!catalog.findComponentNames().contains(componentSchemeAlias.get())) {
// Create an alias for new scheme to the delegate component scheme
// so catalog can be used to build uri
catalog.addComponent(
componentSchemeAlias.get(),
definition.getComponent().getJavaType(),
catalog.componentJSonSchema(componentScheme)
);
}
LOGGER.info("Register component: {} (type: {}) with scheme: {} and alias: {}",
this.componentId,
component.get().getClass().getName(),
this.componentScheme,
this.componentSchemeAlias.get()
);
// remove old component if present
getCamelContext().removeComponent(this.componentSchemeAlias.get());
if (!getCamelContext().hasService(component.get())) {
// ensure component is started and stopped when Camel shutdown if
// not already added
getCamelContext().addService(component.get(), true, true);
}
getCamelContext().addComponent(this.componentSchemeAlias.get(), component.get());
} else {
componentSchemeAlias = Optional.empty();
}
LOGGER.debug("Starting connector: {}", componentId);
super.doStart();
}
@Override
protected void doStop() throws Exception {
if (componentSchemeAlias.isPresent()) {
LOGGER.debug("Stopping component: {}", componentSchemeAlias.get());
getCamelContext().removeComponent(componentSchemeAlias.get());
}
LOGGER.debug("Stopping connector: {}", componentId);
super.doStop();
}
public Processor getBeforeProducer() {
return beforeProducer;
}
public void setBeforeProducer(Processor beforeProducer) {
this.beforeProducer = beforeProducer;
}
public Processor getAfterProducer() {
return afterProducer;
}
public void setAfterProducer(Processor afterProducer) {
this.afterProducer = afterProducer;
}
public Processor getBeforeConsumer() {
return beforeConsumer;
}
public void setBeforeConsumer(Processor beforeConsumer) {
this.beforeConsumer = beforeConsumer;
}
public Processor getAfterConsumer() {
return afterConsumer;
}
public void setAfterConsumer(Processor afterConsumer) {
this.afterConsumer = afterConsumer;
}
public String getComponentId() {
return componentId;
}
public String getComponentScheme() {
return componentScheme;
}
// ***************************************
// Helpers
// ***************************************
/**
* Method used to enrich options before any delegating component/endpoint is created. This is useful
* when some options need to be enforced.
*/
protected void enrichOptions(Map options) {
// no-op
}
protected Optional createDelegateComponent(ComponentDefinition definition, Map options) {
final String componentClass = definition.getComponent().getJavaType();
// configure component with extra options
if (componentClass != null && !options.isEmpty()) {
// Get the list of options from the connector catalog that
// are configured to target the endpoint
final Collection endpointOptions = definition.getEndpointProperties().keySet();
// Check if any of the option applies to the component, if not
// there's no need to create a dedicated component.
boolean hasComponentOptions = options.keySet().stream().anyMatch(Predicates.negate(endpointOptions::contains));
// Options set on a step are strings so if any of the options is
// not a string, is should have been added by a customizer so try to
// bind them to the component first.
boolean hasPojoOptions = options.values().stream().anyMatch(Predicates.negate(String.class::isInstance));
if (hasComponentOptions || hasPojoOptions) {
final CamelContext context = getCamelContext();
// create a new instance of this base component
final Class type = context.getClassResolver().resolveClass(componentClass, Component.class);
final Component component = context.getInjector().newInstance(type);
component.setCamelContext(context);
return Optional.of(component);
}
}
return Optional.empty();
}
protected void configureDelegateComponent(ComponentDefinition definition, Component component, Map options) {
final CamelContext context = getCamelContext();
final List> entries = new ArrayList<>();
// Get the list of options from the connector catalog that
// are configured to target the endpoint
final Collection endpointOptions = definition.getEndpointProperties().keySet();
// Check if any of the option applies to the component, if not
// there's no need to create a dedicated component.
options.entrySet().stream()
.filter(e -> !endpointOptions.contains(e.getKey()))
.forEach(entries::add);
// Options set on a step are strings so if any of the options is
// not a string, is should have been added by a customizer so try to
// bind them to the component first.
options.entrySet().stream()
.filter(e -> e.getValue() != null)
.filter(Predicates.negate(e -> e.getValue() instanceof String))
.forEach(entries::add);
if (!entries.isEmpty()) {
component.setCamelContext(context);
for (Map.Entry entry : entries) {
Object val = entry.getValue();
if (val instanceof String) {
try {
val = getCamelContext().resolvePropertyPlaceholders((String) val);
} catch (Exception e) {
throw new IllegalStateException("Unable to resolve property placeholders in: `" + val + "`", e);
}
}
String key = entry.getKey();
try {
if (IntrospectionSupport.setProperty(context, component, key, val)) {
options.remove(key);
}
} catch (Exception e) {
throw new IllegalStateException("Unable to set property: `" + key+ "` on component: " + component, e);
}
}
}
}
protected Endpoint createDelegateEndpoint(ComponentDefinition definition, String scheme, Map options) {
// Build the delegate uri using the catalog
final String uri;
try {
uri = catalog.asEndpointUri(scheme, options, false);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Unable to create endpoint url for scheme: " + scheme + ", and options: " + options, e);
}
return getCamelContext().getEndpoint(uri);
}
protected void configureDelegateEndpoint(ComponentDefinition definition, Endpoint endpoint, Map options) {
// no-op
}
protected Map buildEndpointOptions(String remaining, Map options) {
final TypeConverter converter = getCamelContext().getTypeConverter();
final Map endpointOptions = new LinkedHashMap<>();
// Extract options from options that are supposed to be set at the endpoint
// level, those options can be overridden and extended using by the query
// parameters.
Collection endpointProperties = getDefinition().getEndpointProperties().keySet();
for (String key : endpointProperties) {
Object val = options.get(key);
if (val != null) {
String converted;
try {
converted = converter.mandatoryConvertTo(String.class, val);
} catch (TypeConversionException | NoTypeConversionAvailableException e) {
throw new IllegalStateException("Unable to convert value: `" + val + "` to String", e);
}
doAddOption(endpointOptions, key, converted);
}
}
// add extra options from remaining (context-path)
if (ObjectHelper.isNotEmpty(remaining)) {
String targetUri = componentScheme + ":" + remaining;
final Map extra;
try {
extra = catalog.endpointProperties(targetUri);
} catch (URISyntaxException e) {
throw new IllegalStateException("Unable to parse endpoint properties from : `" + targetUri+ "`", e);
}
if (extra != null && !extra.isEmpty()) {
extra.forEach((key, value) -> doAddOption(endpointOptions, key, value));
}
}
return endpointOptions;
}
private static void doAddOptions(Map destination, Map options) {
options.forEach(
(k, v) -> doAddOption(destination, k, v)
);
}
private static void doAddOption(Map options, String name, T value) {
LOGGER.trace("Adding option: {}={}", name, value);
T val = options.put(name, value);
if (val != null) {
LOGGER.debug("Options {} overridden, old value was {}", name, val);
}
}
protected Object getOption(String key) {
return configuredOptions.get(key);
}
protected CamelCatalog getCatalog() {
return catalog;
}
// ***************************************
// Extensions
// ***************************************
@Override
public Collection> getSupportedExtensions() {
Set> extensions = new HashSet<>();
extensions.addAll(super.getSupportedExtensions());
extensions.addAll(getCamelContext().getComponent(componentScheme, true, false).getSupportedExtensions()) ;
return extensions;
}
@Override
public Optional getExtension(Class extensionType) {
// first try to grab extensions from component proxy
Optional extension = super.getExtension(extensionType);
if (!extension.isPresent()) {
// then try to grab it from component.
extension = getCamelContext().getComponent(componentScheme, true, false).getExtension(extensionType);
}
return extension;
}
protected final String createEndpointUriFor(final String scheme, final Map options) {
final String uri;
try {
uri = catalog.asEndpointUri(scheme, options, false);
} catch (final URISyntaxException e) {
throw new IllegalArgumentException("Unable to create endpoint url for scheme: " + scheme + ", and options: " + options, e);
}
return uri;
}
/**
* Build a ComponentVerifierExtension using options bound to this component.
*/
private ComponentVerifierExtension getComponentVerifierExtension() {
try {
//
final Component component = getCamelContext().getComponent(componentScheme, true, false);
final Optional extension = component.getExtension(ComponentVerifierExtension.class);
if (extension.isPresent()) {
return (ComponentVerifierExtension.Scope scope, Map map) -> {
Map options;
try {
// A little nasty hack required as verifier uses Map
// to be compatible with all the methods in CamelContext whereas
// catalog deals with Map
@SuppressWarnings("unchecked")
Map tmp = Map.class.cast(buildEndpointOptions(null, map));
options = tmp;
} catch (Exception e) {
// If a failure is detected while reading the catalog, wrap it
// and stop the validation step.
return ResultBuilder.withStatusAndScope(ComponentVerifierExtension.Result.Status.OK, scope)
.error(ResultErrorBuilder.withException(e).build())
.build();
}
return extension.get().verify(scope, options);
};
} else {
return (scope, map) -> {
return ResultBuilder.withStatusAndScope(ComponentVerifierExtension.Result.Status.UNSUPPORTED, scope)
.error(
ResultErrorBuilder.withCode(ComponentVerifierExtension.VerificationError.StandardCode.UNSUPPORTED)
.detail("camel_connector_id", componentId)
.detail("camel_component_scheme", componentScheme)
.detail("camel_component_scheme_alias", componentSchemeAlias)
.build())
.build();
};
}
} catch (Exception e) {
return (scope, map) -> {
return ResultBuilder.withStatusAndScope(ComponentVerifierExtension.Result.Status.OK, scope)
.error(ResultErrorBuilder.withException(e).build())
.build();
};
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy