org.apache.camel.component.undertow.UndertowComponent Maven / Gradle / Ivy
/*
* 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.component.undertow;
import java.net.URI;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.net.ssl.SSLContext;
import io.undertow.server.HttpHandler;
import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.component.extension.ComponentVerifierExtension;
import org.apache.camel.component.undertow.spi.UndertowSecurityProvider;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.RestApiConsumerFactory;
import org.apache.camel.spi.RestConfiguration;
import org.apache.camel.spi.RestConsumerFactory;
import org.apache.camel.spi.RestProducerFactory;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.CamelContextHelper;
import org.apache.camel.support.DefaultComponent;
import org.apache.camel.support.RestComponentHelper;
import org.apache.camel.support.RestProducerFactoryHelper;
import org.apache.camel.support.jsse.SSLContextParameters;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.PropertiesHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.UnsafeUriCharactersEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.camel.support.http.HttpUtil.recreateUrl;
/**
* Represents the component that manages {@link UndertowEndpoint}.
*/
@Metadata(label = "verifiers", enums = "parameters,connectivity")
@Component("undertow")
public class UndertowComponent extends DefaultComponent
implements RestConsumerFactory, RestApiConsumerFactory, RestProducerFactory, SSLContextParametersAware {
private static final Logger LOG = LoggerFactory.getLogger(UndertowComponent.class);
private final Map undertowRegistry = new ConcurrentHashMap<>();
private final Set handlers = new LinkedHashSet<>();
@Metadata(label = "advanced")
private UndertowHttpBinding undertowHttpBinding;
@Metadata(label = "security")
private SSLContextParameters sslContextParameters;
@Metadata(label = "security")
private boolean useGlobalSslContextParameters;
@Metadata(label = "advanced")
private UndertowHostOptions hostOptions;
@Metadata(label = "consumer")
private boolean muteException;
@Metadata(label = "security")
private Object securityConfiguration;
@Metadata(label = "security")
private String allowedRoles;
@Metadata(label = "security")
private UndertowSecurityProvider securityProvider;
public UndertowComponent() {
this(null);
}
public UndertowComponent(CamelContext context) {
super(context);
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception {
URI uriHttpUriAddress = new URI(UnsafeUriCharactersEncoder.encodeHttpURI(remaining));
URI endpointUri = URISupport.createRemainingURI(uriHttpUriAddress, parameters);
// any additional channel options
Map options = PropertiesHelper.extractProperties(parameters, "option.");
// determine sslContextParameters
SSLContextParameters sslParams = this.sslContextParameters;
if (sslParams == null) {
sslParams = retrieveGlobalSslContextParameters();
}
// create the endpoint first
UndertowEndpoint endpoint = createEndpointInstance(endpointUri, this);
// set options from component
endpoint.setSslContextParameters(sslParams);
endpoint.setMuteException(muteException);
// Prefer endpoint configured over component configured
if (undertowHttpBinding == null) {
// fallback to component configured
undertowHttpBinding = getUndertowHttpBinding();
}
if (undertowHttpBinding != null) {
endpoint.setUndertowHttpBinding(undertowHttpBinding);
}
// set options from parameters
setProperties(endpoint, parameters);
if (options != null) {
endpoint.setOptions(options);
}
// then re-create the http uri with the remaining parameters which the endpoint did not use
URI httpUri = URISupport.createRemainingURI(
new URI(
uriHttpUriAddress.getScheme(),
uriHttpUriAddress.getUserInfo(),
uriHttpUriAddress.getHost(),
uriHttpUriAddress.getPort(),
uriHttpUriAddress.getPath(),
uriHttpUriAddress.getQuery(),
uriHttpUriAddress.getFragment()),
parameters);
endpoint.setHttpURI(httpUri);
return endpoint;
}
protected UndertowEndpoint createEndpointInstance(URI endpointUri, UndertowComponent component) {
return new UndertowEndpoint(endpointUri.toString(), component);
}
@Override
public Consumer createConsumer(
CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
String consumes, String produces, RestConfiguration configuration, Map parameters)
throws Exception {
return doCreateConsumer(camelContext, processor, verb, basePath, uriTemplate, consumes, produces, configuration,
parameters, false);
}
@Override
public Consumer createApiConsumer(
CamelContext camelContext, Processor processor, String contextPath,
RestConfiguration configuration, Map parameters)
throws Exception {
// reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
return doCreateConsumer(camelContext, processor, "GET", contextPath, null, null, null, configuration, parameters, true);
}
private void initSecurityProvider() throws Exception {
Object securityConfiguration = getSecurityConfiguration();
if (securityConfiguration != null) {
ServiceLoader securityProvider = ServiceLoader.load(UndertowSecurityProvider.class);
Iterator iter = securityProvider.iterator();
List providers = new LinkedList();
while (iter.hasNext()) {
UndertowSecurityProvider security = iter.next();
//only securityProvider, who accepts security configuration, could be used
if (security.acceptConfiguration(securityConfiguration, null)) {
this.securityProvider = security;
LOG.info("Security provider found {}", securityProvider.getClass().getName());
break;
}
providers.add(security.getClass().getName());
}
if (this.securityProvider == null) {
LOG.info("Security provider for configuration {} not found {}", securityConfiguration, providers);
}
}
}
Consumer doCreateConsumer(
CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
String consumes, String produces, RestConfiguration configuration, Map parameters, boolean api)
throws Exception {
String path = basePath;
if (uriTemplate != null) {
// make sure to avoid double slashes
if (uriTemplate.startsWith("/")) {
path = path + uriTemplate;
} else {
path = path + "/" + uriTemplate;
}
}
path = FileUtil.stripLeadingSeparator(path);
String scheme = "http";
String host = "";
int port = 0;
RestConfiguration config = configuration;
if (config == null) {
config = CamelContextHelper.getRestConfiguration(camelContext, getComponentName());
}
if (config.getScheme() != null) {
scheme = config.getScheme();
}
if (config.getHost() != null) {
host = config.getHost();
}
int num = config.getPort();
if (num > 0) {
port = num;
}
// prefix path with context-path if configured in rest-dsl configuration
String contextPath = config.getContextPath();
if (ObjectHelper.isNotEmpty(contextPath)) {
contextPath = FileUtil.stripTrailingSeparator(contextPath);
contextPath = FileUtil.stripLeadingSeparator(contextPath);
if (ObjectHelper.isNotEmpty(contextPath)) {
path = contextPath + "/" + path;
}
}
// if no explicit hostname set then resolve the hostname
if (ObjectHelper.isEmpty(host)) {
host = RestComponentHelper.resolveRestHostName(host, config);
}
Map map = RestComponentHelper.initRestEndpointProperties(getComponentName(), config);
// build query string, and append any endpoint configuration properties
// must use upper case for restrict
String restrict = verb.toUpperCase(Locale.US);
boolean explicitOptions = restrict.contains("OPTIONS");
boolean cors = config.isEnableCORS();
if (cors) {
// allow HTTP Options as we want to handle CORS in rest-dsl
map.put("optionsEnabled", "true");
} else if (explicitOptions) {
// the rest-dsl is using OPTIONS
map.put("optionsEnabled", "true");
}
if (api) {
map.put("matchOnUriPrefix", "true");
}
RestComponentHelper.addHttpRestrictParam(map, verb, !explicitOptions);
String url = RestComponentHelper.createRestConsumerUrl(getComponentName(), scheme, host, port, path, map);
UndertowEndpoint endpoint = (UndertowEndpoint) camelContext.getEndpoint(url, parameters);
if (!map.containsKey("undertowHttpBinding")) {
// use the rest binding, if not using a custom http binding
endpoint.setUndertowHttpBinding(new RestUndertowHttpBinding(endpoint.isUseStreaming()));
}
// configure consumer properties
Consumer consumer = endpoint.createConsumer(processor);
if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
setProperties(camelContext, consumer, config.getConsumerProperties());
}
if (consumer instanceof UndertowConsumer) {
// mark the consumer as a rest consumer
((UndertowConsumer) consumer).setRest(true);
}
return consumer;
}
@Override
public Producer createProducer(
CamelContext camelContext, String host,
String verb, String basePath, String uriTemplate, String queryParameters,
String consumes, String produces, RestConfiguration configuration, Map parameters)
throws Exception {
// avoid leading slash
basePath = FileUtil.stripLeadingSeparator(basePath);
uriTemplate = FileUtil.stripLeadingSeparator(uriTemplate);
// get the endpoint
String url = getComponentName() + ":" + host;
if (!ObjectHelper.isEmpty(basePath)) {
url += "/" + basePath;
}
if (!ObjectHelper.isEmpty(uriTemplate)) {
url += "/" + uriTemplate;
}
RestConfiguration config = CamelContextHelper.getRestConfiguration(camelContext, null, getComponentName());
Map map = new HashMap<>();
// build query string, and append any endpoint configuration properties
if (config.getProducerComponent() == null || config.getProducerComponent().equals(getComponentName())) {
// setup endpoint options
if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) {
map.putAll(config.getEndpointProperties());
}
}
url = recreateUrl(map, url);
parameters = parameters != null ? new HashMap<>(parameters) : new HashMap<>();
// there are cases where we might end up here without component being created beforehand
// we need to abide by the component properties specified in the parameters when creating
// the component
RestProducerFactoryHelper.setupComponentFor(url, camelContext, (Map) parameters.remove("component"));
UndertowEndpoint endpoint = (UndertowEndpoint) camelContext.getEndpoint(url, parameters);
String path = uriTemplate != null ? uriTemplate : basePath;
endpoint.setHeaderFilterStrategy(new UndertowRestHeaderFilterStrategy(path, queryParameters));
// the endpoint must be started before creating the producer
ServiceHelper.startService(endpoint);
return endpoint.createProducer();
}
@Override
protected void doInit() throws Exception {
super.doInit();
if (this.securityProvider == null) {
initSecurityProvider();
}
try {
RestConfiguration config = CamelContextHelper.getRestConfiguration(getCamelContext(), getComponentName());
// configure additional options on undertow configuration
if (config.getComponentProperties() != null && !config.getComponentProperties().isEmpty()) {
setProperties(this, config.getComponentProperties());
}
} catch (IllegalArgumentException e) {
// if there's a mismatch between the component and the rest-configuration,
// then getRestConfiguration throws IllegalArgumentException which can be
// safely ignored as it means there's no special conf for this component.
}
}
public HttpHandler registerEndpoint(
UndertowConsumer consumer, HttpHandlerRegistrationInfo registrationInfo, SSLContext sslContext, HttpHandler handler)
throws Exception {
final URI uri = registrationInfo.getUri();
final UndertowHostKey key = new UndertowHostKey(uri.getHost(), uri.getPort(), sslContext);
final UndertowHost host = undertowRegistry.computeIfAbsent(key, this::createUndertowHost);
host.validateEndpointURI(uri);
handlers.add(registrationInfo);
HttpHandler handlerWrapped = handler;
if (this.securityProvider != null) {
handlerWrapped = this.securityProvider.wrapHttpHandler(handler);
}
return host.registerHandler(consumer, registrationInfo, handlerWrapped);
}
public void unregisterEndpoint(
UndertowConsumer consumer, HttpHandlerRegistrationInfo registrationInfo, SSLContext sslContext) {
final URI uri = registrationInfo.getUri();
final UndertowHostKey key = new UndertowHostKey(uri.getHost(), uri.getPort(), sslContext);
final UndertowHost host = undertowRegistry.get(key);
handlers.remove(registrationInfo);
// if the route is not automatically started, then the undertow registry
// may not have any instance of UndertowHost associated to the given
// registrationInfo
if (host != null) {
host.unregisterHandler(consumer, registrationInfo);
}
}
protected UndertowHost createUndertowHost(UndertowHostKey key) {
return new DefaultUndertowHost(key, hostOptions);
}
public UndertowHttpBinding getUndertowHttpBinding() {
return undertowHttpBinding;
}
/**
* To use a custom HttpBinding to control the mapping between Camel message and HttpClient.
*/
public void setUndertowHttpBinding(UndertowHttpBinding undertowHttpBinding) {
this.undertowHttpBinding = undertowHttpBinding;
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
/**
* To configure security using SSLContextParameters
*/
public void setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
@Override
public boolean isUseGlobalSslContextParameters() {
return this.useGlobalSslContextParameters;
}
/**
* Enable usage of global SSL context parameters.
*/
@Override
public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
this.useGlobalSslContextParameters = useGlobalSslContextParameters;
}
public UndertowHostOptions getHostOptions() {
return hostOptions;
}
/**
* To configure common options, such as thread pools
*/
public void setHostOptions(UndertowHostOptions hostOptions) {
this.hostOptions = hostOptions;
}
public boolean isMuteException() {
return muteException;
}
/**
* If enabled and an Exchange failed processing on the consumer side the response's body won't contain the
* exception's stack trace.
*/
public void setMuteException(boolean muteException) {
this.muteException = muteException;
}
public ComponentVerifierExtension getVerifier() {
return (scope, parameters) -> getExtension(ComponentVerifierExtension.class)
.orElseThrow(UnsupportedOperationException::new).verify(scope, parameters);
}
protected String getComponentName() {
return "undertow";
}
public Set getHandlers() {
return handlers;
}
public Object getSecurityConfiguration() {
return securityConfiguration;
}
/**
* Configuration used by UndertowSecurityProvider. Security configuration object for use from
* UndertowSecurityProvider. Configuration is UndertowSecurityProvider specific. Each provider decides, whether it
* accepts configuration.
*/
public void setSecurityConfiguration(Object securityConfiguration) {
this.securityConfiguration = securityConfiguration;
}
public String getAllowedRoles() {
return allowedRoles;
}
/**
* Configuration used by UndertowSecurityProvider. Comma separated list of allowed roles.
*/
public void setAllowedRoles(String allowedRoles) {
this.allowedRoles = allowedRoles;
}
/**
* Security provider allows plug in the provider, which will be used to secure requests. SPI approach could be used
* too (component then finds security provider using SPI).
*/
public void setSecurityProvider(UndertowSecurityProvider securityProvider) {
this.securityProvider = securityProvider;
}
public UndertowSecurityProvider getSecurityProvider() {
return securityProvider;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy