/**
* 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.ahc;
import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.camel.Endpoint;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.impl.HeaderFilterStrategyComponent;
import org.apache.camel.spi.Metadata;
import org.apache.camel.util.IntrospectionSupport;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.URISupport;
import org.apache.camel.util.UnsafeUriCharactersEncoder;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.AsyncHttpClientConfig;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Realm;
import org.asynchttpclient.Realm.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* To call external HTTP services using <a href="http://github.com/sonatype/async-http-client">Async Http Client</a>
*/
public class AhcComponent extends HeaderFilterStrategyComponent implements SSLContextParametersAware {
private static final Logger LOG = LoggerFactory.getLogger(AhcComponent.class);
private static final String CLIENT_CONFIG_PREFIX = "clientConfig.";
private static final String CLIENT_REALM_CONFIG_PREFIX = "clientConfig.realm.";
@Metadata(label = "advanced")
private AsyncHttpClient client;
@Metadata(label = "advanced")
private AsyncHttpClientConfig clientConfig;
@Metadata(label = "advanced")
private AhcBinding binding;
@Metadata(label = "security")
private SSLContextParameters sslContextParameters;
@Metadata(label = "security", defaultValue = "false")
private boolean useGlobalSslContextParameters;
@Metadata(label = "advanced")
private boolean allowJavaSerializedObject;
public AhcComponent() {
super(AhcEndpoint.class);
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
String addressUri = createAddressUri(uri, remaining);
SSLContextParameters ssl = getSslContextParameters();
if (ssl == null) {
ssl = retrieveGlobalSslContextParameters();
}
// Do not set the HTTP URI because we still have all of the Camel internal
// parameters in the URI at this point.
AhcEndpoint endpoint = createAhcEndpoint(uri, this, null);
setEndpointHeaderFilterStrategy(endpoint);
endpoint.setClient(getClient());
endpoint.setClientConfig(getClientConfig());
endpoint.setBinding(getBinding());
endpoint.setSslContextParameters(ssl);
setProperties(endpoint, parameters);
if (IntrospectionSupport.hasProperties(parameters, CLIENT_CONFIG_PREFIX)) {
DefaultAsyncHttpClientConfig.Builder builder = endpoint.getClientConfig() == null
? new DefaultAsyncHttpClientConfig.Builder() : AhcComponent.cloneConfig(endpoint.getClientConfig());
if (endpoint.getClient() != null) {
LOG.warn("The user explicitly set an AsyncHttpClient instance on the component or "
+ "endpoint, but this endpoint URI contains client configuration parameters. "
+ "Are you sure that this is what was intended? The AsyncHttpClient will be used"
+ " and the URI parameters will be ignored.");
} else if (endpoint.getClientConfig() != null) {
LOG.warn("The user explicitly set an AsyncHttpClientConfig instance on the component or "
+ "endpoint, but this endpoint URI contains client configuration parameters. "
+ "Are you sure that this is what was intended? The URI parameters will be applied"
+ " to a clone of the supplied AsyncHttpClientConfig in order to prevent unintended modification"
+ " of the explicitly configured AsyncHttpClientConfig. That is, the URI parameters override the"
+ " settings on the explicitly configured AsyncHttpClientConfig for this endpoint.");
}
// special for realm builder
Builder realmBuilder = null;
if (IntrospectionSupport.hasProperties(parameters, CLIENT_REALM_CONFIG_PREFIX)) {
// set and validate additional parameters on client config
Map<String, Object> realmParams = IntrospectionSupport.extractProperties(parameters, CLIENT_REALM_CONFIG_PREFIX);
// copy the parameters for the endpoint to have
endpoint.setClientConfigRealmOptions(new LinkedHashMap<>(realmParams));
Object principal = realmParams.remove("principal");
Object password = realmParams.remove("password");
if (ObjectHelper.isEmpty(principal)) {
throw new IllegalArgumentException(CLIENT_REALM_CONFIG_PREFIX + ".principal must be configured");
}
if (password == null) {
password = "";
}
realmBuilder = new Realm.Builder(principal.toString(), password.toString());
setProperties(realmBuilder, realmParams);
validateParameters(uri, realmParams, null);
}
// set and validate additional parameters on client config
Map<String, Object> clientParams = IntrospectionSupport.extractProperties(parameters, CLIENT_CONFIG_PREFIX);
// copy the parameters for the endpoint to have
endpoint.setClientConfigOptions(new LinkedHashMap<>(clientParams));
setProperties(builder, clientParams);
validateParameters(uri, clientParams, null);
if (realmBuilder != null) {
builder.setRealm(realmBuilder.build());
}
endpoint.setClientConfig(builder.build());
}
// restructure uri to be based on the parameters left as we dont want to include the Camel internal options
addressUri = UnsafeUriCharactersEncoder.encodeHttpURI(addressUri);
URI httpUri = URISupport.createRemainingURI(new URI(addressUri), parameters);
endpoint.setHttpUri(httpUri);
return endpoint;
}
public AsyncHttpClient getClient() {
return client;
}
/**
* To use a custom {@link AsyncHttpClient}
*/
public void setClient(AsyncHttpClient client) {
this.client = client;
}
public AhcBinding getBinding() {
if (binding == null) {
binding = new DefaultAhcBinding();
}
return binding;
}
/**
* To use a custom {@link AhcBinding} which allows to control how to bind between AHC and Camel.
*/
public void setBinding(AhcBinding binding) {
this.binding = binding;
}
public AsyncHttpClientConfig getClientConfig() {
return clientConfig;
}
/**
* To configure the AsyncHttpClient to use a custom com.ning.http.client.AsyncHttpClientConfig instance.
*/
public void setClientConfig(AsyncHttpClientConfig clientConfig) {
this.clientConfig = clientConfig;
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
/**
* Reference to a org.apache.camel.util.jsse.SSLContextParameters in the Registry.
* Note that configuring this option will override any SSL/TLS configuration options provided through the
* clientConfig option at the endpoint or component level.
*/
public void setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
public boolean isAllowJavaSerializedObject() {
return allowJavaSerializedObject;
}
/**
* Whether to allow java serialization when a request uses context-type=application/x-java-serialized-object
* <p/>
* This is by default turned off. If you enable this then be aware that Java will deserialize the incoming
* data from the request to Java and that can be a potential security risk.
*/
public void setAllowJavaSerializedObject(boolean allowJavaSerializedObject) {
this.allowJavaSerializedObject = allowJavaSerializedObject;
}
@Override
public boolean isUseGlobalSslContextParameters() {
return this.useGlobalSslContextParameters;
}
/**
* Enable usage of global SSL context parameters.
*/
@Override
public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
this.useGlobalSslContextParameters = useGlobalSslContextParameters;
}
protected String createAddressUri(String uri, String remaining) {
return remaining;
}
protected AhcEndpoint createAhcEndpoint(String endpointUri, AhcComponent component, URI httpUri) {
return new AhcEndpoint(endpointUri, component, httpUri);
}
/**
* Creates a new client configuration builder using {@code DefaultAsyncHttpClientConfig} as a template for
* the builder.
*
* @param clientConfig the instance to serve as a template for the builder
* @return a builder configured with the same options as the supplied config
*/
static DefaultAsyncHttpClientConfig.Builder cloneConfig(AsyncHttpClientConfig clientConfig) {
DefaultAsyncHttpClientConfig.Builder builder = new DefaultAsyncHttpClientConfig.Builder(clientConfig);
return builder;
}
}