/**
* 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.restlet;
import java.io.IOException;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
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.restlet.converter.RestletConverter;
import org.apache.camel.impl.HeaderFilterStrategyComponent;
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.util.FileUtil;
import org.apache.camel.util.HostUtils;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.ServiceHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.restlet.Component;
import org.restlet.Restlet;
import org.restlet.Server;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.data.Protocol;
import org.restlet.engine.Engine;
import org.restlet.security.ChallengeAuthenticator;
import org.restlet.security.MapVerifier;
import org.restlet.util.Series;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Camel component embedded Restlet that produces and consumes exchanges.
*
* @version
*/
public class RestletComponent extends HeaderFilterStrategyComponent implements RestConsumerFactory, RestApiConsumerFactory, RestProducerFactory, SSLContextParametersAware {
private static final Logger LOG = LoggerFactory.getLogger(RestletComponent.class);
private static final Object LOCK = new Object();
private final Map<String, Server> servers = new HashMap<String, Server>();
private final Map<String, MethodBasedRouter> routers = new HashMap<String, MethodBasedRouter>();
private final Component component;
// options that can be set on the restlet server
@Metadata(label = "consumer,advanced")
private Boolean controllerDaemon;
@Metadata(label = "consumer,advanced")
private Integer controllerSleepTimeMs;
@Metadata(label = "consumer")
private Integer inboundBufferSize;
@Metadata(label = "consumer,advanced")
private Integer minThreads;
@Metadata(label = "consumer,advanced")
private Integer maxThreads;
@Metadata(label = "consumer,advanced")
private Integer lowThreads;
@Metadata(label = "common")
private Integer maxConnectionsPerHost;
@Metadata(label = "common")
private Integer maxTotalConnections;
@Metadata(label = "consumer")
private Integer outboundBufferSize;
@Metadata(label = "consumer,advanced")
private Integer maxQueued;
@Metadata(label = "consumer,advanced")
private Boolean persistingConnections;
@Metadata(label = "consumer,advanced")
private Boolean pipeliningConnections;
@Metadata(label = "consumer,advanced")
private Integer threadMaxIdleTimeMs;
@Metadata(label = "consumer")
private Boolean useForwardedForHeader;
@Metadata(label = "consumer")
private Boolean reuseAddress;
@Metadata(label = "consumer,advanced")
private boolean disableStreamCache;
@Metadata(label = "consumer")
private int port;
@Metadata(label = "producer")
private Boolean synchronous;
@Metadata(label = "advanced")
private List<String> enabledConverters;
@Metadata(label = "security", defaultValue = "false")
private boolean useGlobalSslContextParameters;
public RestletComponent() {
this(new Component());
}
public RestletComponent(Component component) {
// Allow the Component to be injected, so that the RestletServlet may be
// configured within a webapp
super(RestletEndpoint.class);
this.component = component;
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
// grab uri and remove all query parameters as we need to rebuild it a bit special
String endpointUri = uri;
if (endpointUri.indexOf('?') > 0) {
endpointUri = endpointUri.substring(0, endpointUri.indexOf('?'));
}
// normalize so the uri is as expected
endpointUri = URISupport.normalizeUri(endpointUri);
// decode %7B -> {
// decode %7D -> }
endpointUri = endpointUri.replaceAll("%7B", "{").replaceAll("%7D", "}");
// include restlet methods in the uri (use GET as default)
String restletMethods = getAndRemoveParameter(parameters, "restletMethods", String.class);
if (restletMethods != null) {
endpointUri = endpointUri + "?restletMethods=" + restletMethods.toUpperCase();
}
String restletMethod = null;
if (restletMethods == null) {
restletMethod = getAndRemoveParameter(parameters, "restletMethod", String.class, "GET");
endpointUri = endpointUri + "?restletMethod=" + restletMethod.toUpperCase();
}
RestletEndpoint result = new RestletEndpoint(this, endpointUri);
if (synchronous != null) {
result.setSynchronous(synchronous);
}
result.setDisableStreamCache(isDisableStreamCache());
setEndpointHeaderFilterStrategy(result);
setProperties(result, parameters);
if (restletMethods != null) {
result.setRestletMethods(RestletConverter.toMethods(restletMethods));
} else {
result.setRestletMethod(RestletConverter.toMethod(restletMethod));
}
// construct URI so we can use it to get the splitted information
// use raw values to support paths that has spaces
String remainingRaw = URISupport.extractRemainderPath(new URI(uri), true);
URI u = new URI(remainingRaw);
String protocol = u.getScheme();
String uriPattern = URISupport.createRemainingURI(u, parameters).getRawPath();
// must decode back to use {} style as that is what the restlet router expect to match in its uri pattern
// decode %7B -> {
// decode %7D -> }
uriPattern = uriPattern.replaceAll("%7B", "{").replaceAll("%7D", "}");
int port;
String host = u.getHost();
if (u.getPort() > 0) {
port = u.getPort();
} else {
port = this.port;
}
result.setProtocol(protocol);
result.setUriPattern(uriPattern);
result.setHost(host);
if (port > 0) {
result.setPort(port);
}
if (result.getSslContextParameters() == null) {
result.setSslContextParameters(retrieveGlobalSslContextParameters());
}
return result;
}
@Override
protected void doStart() throws Exception {
super.doStart();
// configure component options
RestConfiguration config = getCamelContext().getRestConfiguration("restlet", true);
// configure additional options on spark configuration
if (config.getComponentProperties() != null && !config.getComponentProperties().isEmpty()) {
setProperties(this, config.getComponentProperties());
}
cleanupConverters(enabledConverters);
component.start();
}
@Override
protected void doStop() throws Exception {
component.stop();
// component stop will stop servers so we should clear our list as well
servers.clear();
// routers map entries are removed as consumer stops and servers map
// is not touch so to keep in sync with component's servers
super.doStop();
}
@Override
protected boolean useIntrospectionOnEndpoint() {
// we invoke setProperties ourselves so we can construct "user" uri on
// on the remaining parameters
return false;
}
public void connect(RestletConsumer consumer) throws Exception {
RestletEndpoint endpoint = consumer.getEndpoint();
addServerIfNecessary(endpoint);
// if restlet servlet server is created, the offsetPath is set in component context
// see http://restlet.tigris.org/issues/show_bug.cgi?id=988
String offsetPath = (String) this.component.getContext()
.getAttributes().get("org.restlet.ext.servlet.offsetPath");
if (endpoint.getUriPattern() != null && endpoint.getUriPattern().length() > 0) {
attachUriPatternToRestlet(offsetPath, endpoint.getUriPattern(), endpoint, consumer.getRestlet());
}
if (endpoint.getRestletUriPatterns() != null) {
for (String uriPattern : endpoint.getRestletUriPatterns()) {
attachUriPatternToRestlet(offsetPath, uriPattern, endpoint, consumer.getRestlet());
}
}
}
public void disconnect(RestletConsumer consumer) throws Exception {
RestletEndpoint endpoint = consumer.getEndpoint();
List<MethodBasedRouter> routesToRemove = new ArrayList<MethodBasedRouter>();
String pattern = endpoint.getUriPattern();
if (pattern != null && !pattern.isEmpty()) {
MethodBasedRouter methodRouter = getMethodRouter(pattern, false);
if (methodRouter != null) {
routesToRemove.add(methodRouter);
}
}
if (endpoint.getRestletUriPatterns() != null) {
for (String uriPattern : endpoint.getRestletUriPatterns()) {
MethodBasedRouter methodRouter = getMethodRouter(uriPattern, false);
if (methodRouter != null) {
routesToRemove.add(methodRouter);
}
}
}
for (MethodBasedRouter router : routesToRemove) {
if (endpoint.getRestletMethods() != null) {
Method[] methods = endpoint.getRestletMethods();
for (Method method : methods) {
router.removeRoute(method);
}
} else {
router.removeRoute(endpoint.getRestletMethod());
}
if (LOG.isDebugEnabled()) {
LOG.debug("Detached restlet uriPattern: {} method: {}", router.getUriPattern(),
endpoint.getRestletMethod());
}
// remove router if its no longer in use
if (!router.hasRoutes()) {
deAttachUriPatternFromRestlet(router.getUriPattern(), endpoint, router);
if (!router.isStopped()) {
router.stop();
}
routers.remove(router.getUriPattern());
}
}
}
private MethodBasedRouter getMethodRouter(String uriPattern, boolean addIfEmpty) {
synchronized (routers) {
MethodBasedRouter result = routers.get(uriPattern);
if (result == null && addIfEmpty) {
result = new MethodBasedRouter(uriPattern);
LOG.debug("Added method based router: {}", result);
routers.put(uriPattern, result);
}
return result;
}
}
protected Server createServer(RestletEndpoint endpoint) {
// Consider hostname if provided. This is useful when loopback interface is required for security reasons.
if (endpoint.getHost() != null) {
return new Server(component.getContext().createChildContext(), Protocol.valueOf(endpoint.getProtocol()), endpoint.getHost(), endpoint.getPort(), null);
} else {
return new Server(component.getContext().createChildContext(), Protocol.valueOf(endpoint.getProtocol()), endpoint.getPort());
}
}
protected String stringArrayToString(String[] strings) {
StringBuffer result = new StringBuffer();
for (String str : strings) {
result.append(str);
result.append(" ");
}
return result.toString();
}
protected void setupServerWithSSLContext(Series<Parameter> params, SSLContextParameters scp) throws GeneralSecurityException, IOException {
// set the SSLContext parameters
params.add("sslContextFactory",
"org.restlet.engine.ssl.DefaultSslContextFactory");
SSLContext context = scp.createSSLContext(getCamelContext());
SSLEngine engine = context.createSSLEngine();
params.add("enabledProtocols", stringArrayToString(engine.getEnabledProtocols()));
params.add("enabledCipherSuites", stringArrayToString(engine.getEnabledCipherSuites()));
if (scp.getSecureSocketProtocol() != null) {
params.add("protocol", scp.getSecureSocketProtocol());
}
if (scp.getServerParameters() != null && scp.getServerParameters().getClientAuthentication() != null) {
boolean b = !scp.getServerParameters().getClientAuthentication().equals("NONE");
params.add("needClientAuthentication", String.valueOf(b));
}
if (scp.getKeyManagers() != null) {
if (scp.getKeyManagers().getAlgorithm() != null) {
params.add("keyManagerAlgorithm", scp.getKeyManagers().getAlgorithm());
}
if (scp.getKeyManagers().getKeyPassword() != null) {
params.add("keyPassword", scp.getKeyManagers().getKeyPassword());
}
if (scp.getKeyManagers().getKeyStore().getResource() != null) {
params.add("keyStorePath", scp.getKeyManagers().getKeyStore().getResource());
}
if (scp.getKeyManagers().getKeyStore().getPassword() != null) {
params.add("keyStorePassword", scp.getKeyManagers().getKeyStore().getPassword());
}
if (scp.getKeyManagers().getKeyStore().getType() != null) {
params.add("keyStoreType", scp.getKeyManagers().getKeyStore().getType());
}
}
if (scp.getTrustManagers() != null) {
if (scp.getTrustManagers().getAlgorithm() != null) {
params.add("trustManagerAlgorithm", scp.getKeyManagers().getAlgorithm());
}
if (scp.getTrustManagers().getKeyStore().getResource() != null) {
params.add("trustStorePath", scp.getTrustManagers().getKeyStore().getResource());
}
if (scp.getTrustManagers().getKeyStore().getPassword() != null) {
params.add("trustStorePassword", scp.getTrustManagers().getKeyStore().getPassword());
}
if (scp.getTrustManagers().getKeyStore().getType() != null) {
params.add("trustStoreType", scp.getTrustManagers().getKeyStore().getType());
}
}
}
protected void addServerIfNecessary(RestletEndpoint endpoint) throws Exception {
String key = buildKey(endpoint);
Server server;
synchronized (servers) {
server = servers.get(key);
if (server == null) {
server = createServer(endpoint);
component.getServers().add(server);
// Add any Restlet server parameters that were included
Series<Parameter> params = server.getContext().getParameters();
if ("https".equals(endpoint.getProtocol())) {
SSLContextParameters scp = endpoint.getSslContextParameters();
if (endpoint.getSslContextParameters() == null) {
throw new InvalidParameterException("Need to specify the SSLContextParameters option here!");
}
setupServerWithSSLContext(params, scp);
}
if (getControllerDaemon() != null) {
params.add("controllerDaemon", getControllerDaemon().toString());
}
if (getControllerSleepTimeMs() != null) {
params.add("controllerSleepTimeMs", getControllerSleepTimeMs().toString());
}
if (getInboundBufferSize() != null) {
params.add("inboundBufferSize", getInboundBufferSize().toString());
}
if (getMinThreads() != null) {
params.add("minThreads", getMinThreads().toString());
}
if (getMaxThreads() != null) {
params.add("maxThreads", getMaxThreads().toString());
}
if (getLowThreads() != null) {
params.add("lowThreads", getLowThreads().toString());
}
if (getMaxQueued() != null) {
params.add("maxQueued", getMaxQueued().toString());
}
if (getMaxConnectionsPerHost() != null) {
params.add("maxConnectionsPerHost", getMaxConnectionsPerHost().toString());
}
if (getMaxTotalConnections() != null) {
params.add("maxTotalConnections", getMaxTotalConnections().toString());
}
if (getOutboundBufferSize() != null) {
params.add("outboundBufferSize", getOutboundBufferSize().toString());
}
if (getPersistingConnections() != null) {
params.add("persistingConnections", getPersistingConnections().toString());
}
if (getPipeliningConnections() != null) {
params.add("pipeliningConnections", getPipeliningConnections().toString());
}
if (getThreadMaxIdleTimeMs() != null) {
params.add("threadMaxIdleTimeMs", getThreadMaxIdleTimeMs().toString());
}
if (getUseForwardedForHeader() != null) {
params.add("useForwardedForHeader", getUseForwardedForHeader().toString());
}
if (getReuseAddress() != null) {
params.add("reuseAddress", getReuseAddress().toString());
}
LOG.debug("Setting parameters: {} to server: {}", params, server);
server.getContext().setParameters(params);
servers.put(key, server);
LOG.debug("Added server: {}", key);
server.start();
}
}
}
private static String buildKey(RestletEndpoint endpoint) {
return endpoint.getHost() + ":" + endpoint.getPort();
}
private void attachUriPatternToRestlet(String offsetPath, String uriPattern, RestletEndpoint endpoint, Restlet target) throws Exception {
MethodBasedRouter router = getMethodRouter(uriPattern, true);
Map<String, String> realm = endpoint.getRestletRealm();
if (realm != null && realm.size() > 0) {
ChallengeAuthenticator guard = new ChallengeAuthenticator(component.getContext()
.createChildContext(), ChallengeScheme.HTTP_BASIC, "Camel-Restlet Endpoint Realm");
MapVerifier verifier = new MapVerifier();
for (Map.Entry<String, String> entry : realm.entrySet()) {
verifier.getLocalSecrets().put(entry.getKey(), entry.getValue().toCharArray());
}
guard.setVerifier(verifier);
guard.setNext(target);
target = guard;
LOG.debug("Target has been set to guard: {}", guard);
}
if (endpoint.getRestletMethods() != null) {
Method[] methods = endpoint.getRestletMethods();
for (Method method : methods) {
router.addRoute(method, target);
LOG.debug("Attached restlet uriPattern: {} method: {}", uriPattern, method);
}
} else {
Method method = endpoint.getRestletMethod();
router.addRoute(method, target);
LOG.debug("Attached restlet uriPattern: {} method: {}", uriPattern, method);
}
if (!router.hasBeenAttached()) {
component.getDefaultHost().attach(
offsetPath == null ? uriPattern : offsetPath + uriPattern, router);
LOG.debug("Attached methodRouter uriPattern: {}", uriPattern);
}
if (!router.isStarted()) {
router.start();
LOG.debug("Started methodRouter uriPattern: {}", uriPattern);
}
}
private void deAttachUriPatternFromRestlet(String uriPattern, RestletEndpoint endpoint, Restlet target) throws Exception {
component.getDefaultHost().detach(target);
LOG.debug("De-attached methodRouter uriPattern: {}", uriPattern);
}
public Boolean getControllerDaemon() {
return controllerDaemon;
}
/**
* Indicates if the controller thread should be a daemon (not blocking JVM exit).
*/
public void setControllerDaemon(Boolean controllerDaemon) {
this.controllerDaemon = controllerDaemon;
}
public Integer getControllerSleepTimeMs() {
return controllerSleepTimeMs;
}
/**
* Time for the controller thread to sleep between each control.
*/
public void setControllerSleepTimeMs(Integer controllerSleepTimeMs) {
this.controllerSleepTimeMs = controllerSleepTimeMs;
}
public Integer getInboundBufferSize() {
return inboundBufferSize;
}
/**
* The size of the buffer when reading messages.
*/
public void setInboundBufferSize(Integer inboundBufferSize) {
this.inboundBufferSize = inboundBufferSize;
}
public Integer getMaxConnectionsPerHost() {
return maxConnectionsPerHost;
}
/**
* Maximum number of concurrent connections per host (IP address).
*/
public void setMaxConnectionsPerHost(Integer maxConnectionsPerHost) {
this.maxConnectionsPerHost = maxConnectionsPerHost;
}
public Integer getMaxThreads() {
return maxThreads;
}
/**
* Maximum threads that will service requests.
*/
public void setMaxThreads(Integer maxThreads) {
this.maxThreads = maxThreads;
}
public Integer getLowThreads() {
return lowThreads;
}
/**
* Number of worker threads determining when the connector is considered overloaded.
*/
public void setLowThreads(Integer lowThreads) {
this.lowThreads = lowThreads;
}
public Integer getMaxTotalConnections() {
return maxTotalConnections;
}
/**
* Maximum number of concurrent connections in total.
*/
public void setMaxTotalConnections(Integer maxTotalConnections) {
this.maxTotalConnections = maxTotalConnections;
}
public Integer getMinThreads() {
return minThreads;
}
/**
* Minimum threads waiting to service requests.
*/
public void setMinThreads(Integer minThreads) {
this.minThreads = minThreads;
}
public Integer getOutboundBufferSize() {
return outboundBufferSize;
}
/**
* The size of the buffer when writing messages.
*/
public void setOutboundBufferSize(Integer outboundBufferSize) {
this.outboundBufferSize = outboundBufferSize;
}
public Boolean getPersistingConnections() {
return persistingConnections;
}
/**
* Indicates if connections should be kept alive after a call.
*/
public void setPersistingConnections(Boolean persistingConnections) {
this.persistingConnections = persistingConnections;
}
public Boolean getPipeliningConnections() {
return pipeliningConnections;
}
/**
* Indicates if pipelining connections are supported.
*/
public void setPipeliningConnections(Boolean pipeliningConnections) {
this.pipeliningConnections = pipeliningConnections;
}
public Integer getThreadMaxIdleTimeMs() {
return threadMaxIdleTimeMs;
}
/**
* Time for an idle thread to wait for an operation before being collected.
*/
public void setThreadMaxIdleTimeMs(Integer threadMaxIdleTimeMs) {
this.threadMaxIdleTimeMs = threadMaxIdleTimeMs;
}
public Boolean getUseForwardedForHeader() {
return useForwardedForHeader;
}
/**
* Lookup the "X-Forwarded-For" header supported by popular proxies and caches and uses it to populate the Request.getClientAddresses()
* method result. This information is only safe for intermediary components within your local network.
* Other addresses could easily be changed by setting a fake header and should not be trusted for serious security checks.
*/
public void setUseForwardedForHeader(Boolean useForwardedForHeader) {
this.useForwardedForHeader = useForwardedForHeader;
}
public Boolean getReuseAddress() {
return reuseAddress;
}
/**
* Enable/disable the SO_REUSEADDR socket option.
* See java.io.ServerSocket#reuseAddress property for additional details.
*/
public void setReuseAddress(Boolean reuseAddress) {
this.reuseAddress = reuseAddress;
}
public Integer getMaxQueued() {
return maxQueued;
}
/**
* Maximum number of calls that can be queued if there aren't any worker thread available to service them.
* If the value is '0', then no queue is used and calls are rejected if no worker thread is immediately available.
* If the value is '-1', then an unbounded queue is used and calls are never rejected.
*/
public void setMaxQueued(Integer maxQueued) {
this.maxQueued = maxQueued;
}
public boolean isDisableStreamCache() {
return disableStreamCache;
}
/**
* Determines whether or not the raw input stream from Restlet is cached or not
* (Camel will read the stream into a in memory/overflow to file, Stream caching) cache.
* By default Camel will cache the Restlet input stream to support reading it multiple times to ensure Camel
* can retrieve all data from the stream. However you can set this option to true when you for example need
* to access the raw stream, such as streaming it directly to a file or other persistent store.
* DefaultRestletBinding will copy the request input stream into a stream cache and put it into message body
* if this option is false to support reading the stream multiple times.
*/
public void setDisableStreamCache(boolean disableStreamCache) {
this.disableStreamCache = disableStreamCache;
}
public int getPort() {
return port;
}
/**
* To configure the port number for the restlet consumer routes.
* This allows to configure this once to reuse the same port for these consumers.
*/
public void setPort(int port) {
this.port = port;
}
public Boolean getSynchronous() {
return synchronous;
}
/**
* Whether to use synchronous Restlet Client for the producer. Setting this option to true can yield faster performance
* as it seems the Restlet synchronous Client works better.
*/
public void setSynchronous(Boolean synchronous) {
this.synchronous = synchronous;
}
public List<String> getEnabledConverters() {
return enabledConverters;
}
/**
* A list of converters to enable as full class name or simple class name.
* All the converters automatically registered are enabled if empty or null
*/
public void setEnabledConverters(List<String> enabledConverters) {
if (enabledConverters != null && !enabledConverters.isEmpty()) {
this.enabledConverters = new ArrayList(enabledConverters);
}
}
/**
* A comma separated list of converters to enable as full class name or simple
* class name. All the converters automatically registered are enabled if
* empty or null
*/
public void setEnabledConverters(String enabledConverters) {
if (ObjectHelper.isNotEmpty(enabledConverters)) {
this.enabledConverters = Arrays.asList(enabledConverters.split(","));
}
}
@Override
public boolean isUseGlobalSslContextParameters() {
return this.useGlobalSslContextParameters;
}
/**
* Enable usage of global SSL context parameters.
*/
@Override
public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
this.useGlobalSslContextParameters = useGlobalSslContextParameters;
}
@Override
public Consumer createConsumer(CamelContext camelContext, Processor processor, String verb, String basePath, String uriTemplate,
String consumes, String produces, RestConfiguration configuration, Map<String, Object> parameters) 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 = "";
// use the component's port as the default value
int port = this.getPort();
// if no explicit port/host configured, then use port from rest configuration
RestConfiguration config = configuration;
if (config == null) {
config = camelContext.getRestConfiguration("restlet", true);
}
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)) {
if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.allLocalIp) {
host = "0.0.0.0";
} else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localHostName) {
host = HostUtils.getLocalHostName();
} else if (config.getRestHostNameResolver() == RestConfiguration.RestHostNameResolver.localIp) {
host = HostUtils.getLocalIp();
}
}
Map<String, Object> map = new HashMap<String, Object>();
// build query string, and append any endpoint configuration properties
if (config.getComponent() == null || config.getComponent().equals("restlet")) {
// setup endpoint options
if (config.getEndpointProperties() != null && !config.getEndpointProperties().isEmpty()) {
map.putAll(config.getEndpointProperties());
}
}
// allow HTTP Options as we want to handle CORS in rest-dsl
boolean cors = config.isEnableCORS();
String query = URISupport.createQueryString(map);
String url;
// must use upper case for restrict
String restrict = verb.toUpperCase(Locale.US);
if (cors) {
restrict += ",OPTIONS";
}
if (port > 0) {
url = "restlet:%s://%s:%s/%s?restletMethods=%s";
url = String.format(url, scheme, host, port, path, restrict);
} else {
// It could use the restlet servlet transport
url = "restlet:/%s?restletMethods=%s";
url = String.format(url, path, restrict);
}
if (!query.isEmpty()) {
url = url + "&" + query;
}
// get the endpoint
RestletEndpoint endpoint = camelContext.getEndpoint(url, RestletEndpoint.class);
setProperties(camelContext, endpoint, parameters);
// the endpoint must be started before creating the consumer
ServiceHelper.startService(endpoint);
// configure consumer properties
Consumer consumer = endpoint.createConsumer(processor);
if (config.getConsumerProperties() != null && !config.getConsumerProperties().isEmpty()) {
setProperties(camelContext, consumer, config.getConsumerProperties());
}
return consumer;
}
@Override
public Consumer createApiConsumer(CamelContext camelContext, Processor processor, String contextPath,
RestConfiguration configuration, Map<String, Object> parameters) throws Exception {
// reuse the createConsumer method we already have. The api need to use GET and match on uri prefix
return createConsumer(camelContext, processor, "GET", contextPath, null, null, null, configuration, parameters);
}
@Override
public Producer createProducer(CamelContext camelContext, String host,
String verb, String basePath, String uriTemplate, String queryParameters,
String consumes, String produces, Map<String, Object> parameters) throws Exception {
// avoid leading slash
basePath = FileUtil.stripLeadingSeparator(basePath);
uriTemplate = FileUtil.stripLeadingSeparator(uriTemplate);
// restlet method must be in upper-case
String restletMethod = verb.toUpperCase(Locale.US);
// get the endpoint
String url = "restlet:" + host;
if (!ObjectHelper.isEmpty(basePath)) {
url += "/" + basePath;
}
if (!ObjectHelper.isEmpty(uriTemplate)) {
url += "/" + uriTemplate;
}
url += "?restletMethod=" + restletMethod;
RestletEndpoint endpoint = camelContext.getEndpoint(url, RestletEndpoint.class);
if (parameters != null && !parameters.isEmpty()) {
setProperties(camelContext, endpoint, parameters);
}
// the endpoint must be started before creating the producer
ServiceHelper.startService(endpoint);
return endpoint.createProducer();
}
protected static void cleanupConverters(List<String> converters) {
if (converters != null && !converters.isEmpty()) {
// To avoid race conditions this operation relies on a global lock, we
// could have used Engine's instance as lock so we'd lock only operations
// on the same instance but we do not know how Engine is used by the
// restlet framework
synchronized (LOCK) {
Engine.getInstance().getRegisteredConverters().removeIf(
converter ->
!converters.contains(converter.getClass().getName())
&& !converters.contains(converter.getClass().getSimpleName())
);
}
}
}
}