Return-Path: X-Original-To: apmail-camel-commits-archive@www.apache.org Delivered-To: apmail-camel-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8FD9617350 for ; Tue, 24 Mar 2015 22:17:20 +0000 (UTC) Received: (qmail 13603 invoked by uid 500); 24 Mar 2015 22:17:20 -0000 Delivered-To: apmail-camel-commits-archive@camel.apache.org Received: (qmail 13557 invoked by uid 500); 24 Mar 2015 22:17:20 -0000 Mailing-List: contact commits-help@camel.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@camel.apache.org Delivered-To: mailing list commits@camel.apache.org Received: (qmail 13548 invoked by uid 99); 24 Mar 2015 22:17:20 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 24 Mar 2015 22:17:20 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 53655E10B2; Tue, 24 Mar 2015 22:17:20 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: dhirajsb@apache.org To: commits@camel.apache.org Message-Id: <53561d90417d4215a11f56441bb02682@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: camel git commit: CAMEL-8543: Added support for configuring Salesforce HTTP client, HTTP proxy, added HTTP proxy integration tests Date: Tue, 24 Mar 2015 22:17:20 +0000 (UTC) Repository: camel Updated Branches: refs/heads/camel-2.13.x 8af10cf1f -> ede4a72b7 CAMEL-8543: Added support for configuring Salesforce HTTP client, HTTP proxy, added HTTP proxy integration tests Conflicts: components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/ede4a72b Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/ede4a72b Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/ede4a72b Branch: refs/heads/camel-2.13.x Commit: ede4a72b7873126143a7ee28b2447ad376dc8658 Parents: 8af10cf Author: Dhiraj Bokde Authored: Tue Mar 24 14:26:09 2015 -0700 Committer: Dhiraj Bokde Committed: Tue Mar 24 14:36:20 2015 -0700 ---------------------------------------------------------------------- .../camel-salesforce-component/pom.xml | 6 + .../salesforce/SalesforceComponent.java | 99 ++++++- .../salesforce/SalesforceEndpoint.java | 36 +++ .../salesforce/SalesforceEndpointConfig.java | 2 +- .../salesforce/AbstractSalesforceTestBase.java | 4 +- .../salesforce/HttpProxyIntegrationTest.java | 154 ++++++++++ .../camel-salesforce-maven-plugin/pom.xml | 6 + .../src/it/simple-it/pom.xml | 14 + .../apache/camel/maven/CamelSalesforceMojo.java | 297 +++++++++++++------ .../CamelSalesforceMojoIntegrationTest.java | 23 +- .../maven/HttpProxyMojoIntegrationTest.java | 110 +++++++ 11 files changed, 631 insertions(+), 120 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/pom.xml b/components/camel-salesforce/camel-salesforce-component/pom.xml index 03f8dd0..147d355 100644 --- a/components/camel-salesforce/camel-salesforce-component/pom.xml +++ b/components/camel-salesforce/camel-salesforce-component/pom.xml @@ -111,6 +111,12 @@ camel-test test + + org.eclipse.jetty + jetty-server + ${jetty-version} + test + http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java index c12edc2..8d43b70 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java +++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceComponent.java @@ -38,12 +38,15 @@ import org.apache.camel.component.salesforce.internal.SalesforceSession; import org.apache.camel.component.salesforce.internal.streaming.SubscriptionHelper; import org.apache.camel.impl.UriEndpointComponent; import org.apache.camel.spi.EndpointCompleter; +import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.ReflectionHelper; import org.apache.camel.util.ServiceHelper; import org.apache.camel.util.jsse.SSLContextParameters; +import org.eclipse.jetty.client.Address; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.RedirectListener; +import org.eclipse.jetty.client.security.ProxyAuthorization; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,16 +58,29 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin private static final Logger LOG = LoggerFactory.getLogger(SalesforceComponent.class); - private static final int MAX_CONNECTIONS_PER_ADDRESS = 20; private static final int CONNECTION_TIMEOUT = 60000; - private static final int RESPONSE_TIMEOUT = 60000; + private static final long RESPONSE_TIMEOUT = 60000; private static final Pattern SOBJECT_NAME_PATTERN = Pattern.compile("^.*[\\?&]sObjectName=([^&,]+).*$"); private static final String APEX_CALL_PREFIX = OperationName.APEX_CALL.value() + "/"; private SalesforceLoginConfig loginConfig; private SalesforceEndpointConfig config; + // HTTP client parameters, map of property-name to value + private Map httpClientProperties; + + // SSL parameters private SSLContextParameters sslContextParameters; + + // Proxy host and port + private String httpProxyHost; + private Integer httpProxyPort; + + // Proxy basic authentication + private String httpProxyUsername; + private String httpProxyPassword; + + // DTO packages to scan private String[] packages; // component state @@ -127,10 +143,12 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin // if operation is APEX call, map remaining parameters to query params if (operationName == OperationName.APEX_CALL && !parameters.isEmpty()) { - Map queryParams = new HashMap(parameters); + Map queryParams = new HashMap(copy.getApexQueryParams()); + + // override component params with endpoint params + queryParams.putAll(parameters); parameters.clear(); - queryParams.putAll(copy.getApexQueryParams()); copy.setApexQueryParams(queryParams); } @@ -161,18 +179,34 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin if (config != null && config.getHttpClient() != null) { httpClient = config.getHttpClient(); } else { - final SslContextFactory sslContextFactory = new SslContextFactory(); - final SSLContextParameters contextParameters = - sslContextParameters != null ? sslContextParameters : new SSLContextParameters(); - sslContextFactory.setSslContext(contextParameters.createSSLContext()); - httpClient = new HttpClient(sslContextFactory); + httpClient = new HttpClient(); + // default settings httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); - httpClient.setMaxConnectionsPerAddress(MAX_CONNECTIONS_PER_ADDRESS); httpClient.setConnectTimeout(CONNECTION_TIMEOUT); httpClient.setTimeout(RESPONSE_TIMEOUT); } } + // set ssl context parameters + final SSLContextParameters contextParameters = sslContextParameters != null + ? sslContextParameters : new SSLContextParameters(); + final SslContextFactory sslContextFactory = httpClient.getSslContextFactory(); + sslContextFactory.setSslContext(contextParameters.createSSLContext()); + + // set HTTP client parameters + if (httpClientProperties != null && !httpClientProperties.isEmpty()) { + IntrospectionSupport.setProperties(getCamelContext().getTypeConverter(), + httpClient, new HashMap(httpClientProperties)); + } + + // set HTTP proxy settings + if (this.httpProxyHost != null && httpProxyPort != null) { + httpClient.setProxy(new Address(this.httpProxyHost, this.httpProxyPort)); + } + if (this.httpProxyUsername != null && httpProxyPassword != null) { + httpClient.setProxyAuthentication(new ProxyAuthorization(this.httpProxyUsername, this.httpProxyPassword)); + } + // add redirect listener to handle Salesforce redirects // this is ok to do since the RedirectListener is in the same classloader as Jetty client String listenerClass = RedirectListener.class.getName(); @@ -235,7 +269,10 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin if (httpClient != null) { // shutdown http client connections httpClient.stop(); - httpClient.destroy(); + // destroy http client if it was created by the component + if (config.getHttpClient() == null) { + httpClient.destroy(); + } httpClient = null; } } @@ -332,6 +369,14 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin this.config = config; } + public Map getHttpClientProperties() { + return httpClientProperties; + } + + public void setHttpClientProperties(Map httpClientProperties) { + this.httpClientProperties = httpClientProperties; + } + public SSLContextParameters getSslContextParameters() { return sslContextParameters; } @@ -340,6 +385,38 @@ public class SalesforceComponent extends UriEndpointComponent implements Endpoin this.sslContextParameters = sslContextParameters; } + public String getHttpProxyHost() { + return httpProxyHost; + } + + public void setHttpProxyHost(String httpProxyHost) { + this.httpProxyHost = httpProxyHost; + } + + public Integer getHttpProxyPort() { + return httpProxyPort; + } + + public void setHttpProxyPort(Integer httpProxyPort) { + this.httpProxyPort = httpProxyPort; + } + + public String getHttpProxyUsername() { + return httpProxyUsername; + } + + public void setHttpProxyUsername(String httpProxyUsername) { + this.httpProxyUsername = httpProxyUsername; + } + + public String getHttpProxyPassword() { + return httpProxyPassword; + } + + public void setHttpProxyPassword(String httpProxyPassword) { + this.httpProxyPassword = httpProxyPassword; + } + public String[] getPackages() { return packages; } http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java index 6043d2b..2e63f49 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java +++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpoint.java @@ -24,6 +24,9 @@ import org.apache.camel.impl.DefaultEndpoint; import org.apache.camel.impl.SynchronousDelegateProducer; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; +import org.eclipse.jetty.client.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Represents a Salesforce endpoint. @@ -31,6 +34,8 @@ import org.apache.camel.spi.UriParam; @UriEndpoint(scheme = "salesforce", consumerClass = SalesforceConsumer.class) public class SalesforceEndpoint extends DefaultEndpoint { + private static final Logger LOG = LoggerFactory.getLogger(SalesforceEndpoint.class); + @UriParam private final SalesforceEndpointConfig config; @@ -96,4 +101,35 @@ public class SalesforceEndpoint extends DefaultEndpoint { return topicName; } + @Override + protected void doStart() throws Exception { + try { + super.doStart(); + } finally { + // check if this endpoint has its own http client that needs to be started + final HttpClient httpClient = getConfiguration().getHttpClient(); + if (httpClient != null && getComponent().getConfig().getHttpClient() != httpClient) { + final String endpointUri = getEndpointUri(); + LOG.debug("Starting http client for {} ...", endpointUri); + httpClient.start(); + LOG.debug("Started http client for {}", endpointUri); + } + } + } + + @Override + protected void doStop() throws Exception { + try { + super.doStop(); + } finally { + // check if this endpoint has its own http client that needs to be stopped + final HttpClient httpClient = getConfiguration().getHttpClient(); + if (httpClient != null && getComponent().getConfig().getHttpClient() != httpClient) { + final String endpointUri = getEndpointUri(); + LOG.debug("Stopping http client for {} ...", endpointUri); + httpClient.stop(); + LOG.debug("Stopped http client for {}", endpointUri); + } + } + } } http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java index 9af05b9..39b860a 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java +++ b/components/camel-salesforce/camel-salesforce-component/src/main/java/org/apache/camel/component/salesforce/SalesforceEndpointConfig.java @@ -129,7 +129,7 @@ public class SalesforceEndpointConfig implements Cloneable { public SalesforceEndpointConfig copy() { try { final SalesforceEndpointConfig copy = (SalesforceEndpointConfig) super.clone(); - // nothing to deep copy + // nothing to deep copy, getApexQueryParams() is readonly, so no need to deep copy return copy; } catch (CloneNotSupportedException ex) { throw new RuntimeCamelException(ex); http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/AbstractSalesforceTestBase.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/AbstractSalesforceTestBase.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/AbstractSalesforceTestBase.java index 52d1d65..f05bbf9 100644 --- a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/AbstractSalesforceTestBase.java +++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/AbstractSalesforceTestBase.java @@ -16,8 +16,6 @@ */ package org.apache.camel.component.salesforce; -import java.io.IOException; - import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.salesforce.dto.generated.Merchandise__c; import org.apache.camel.test.junit4.CamelTestSupport; @@ -40,7 +38,7 @@ public abstract class AbstractSalesforceTestBase extends CamelTestSupport { protected abstract RouteBuilder doCreateRouteBuilder() throws Exception; - protected void createComponent() throws IllegalAccessException, IOException { + protected void createComponent() throws Exception { // create the component SalesforceComponent component = new SalesforceComponent(); final SalesforceEndpointConfig config = new SalesforceEndpointConfig(); http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/HttpProxyIntegrationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/HttpProxyIntegrationTest.java b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/HttpProxyIntegrationTest.java new file mode 100644 index 0000000..7b237cd --- /dev/null +++ b/components/camel-salesforce/camel-salesforce-component/src/test/java/org/apache/camel/component/salesforce/HttpProxyIntegrationTest.java @@ -0,0 +1,154 @@ +/** + * 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.salesforce; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.salesforce.api.dto.Version; +import org.apache.camel.component.salesforce.api.dto.Versions; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ConnectHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Test HTTP proxy configuration for Salesforce component. + */ +public class HttpProxyIntegrationTest extends AbstractSalesforceTestBase { + + private static final Logger LOG = LoggerFactory.getLogger(HttpProxyIntegrationTest.class); + private static final String HTTP_PROXY_HOST = "localhost"; + private static final String HTTP_PROXY_USER_NAME = "camel-user"; + private static final String HTTP_PROXY_PASSWORD = "camel-user-password"; + + private static Server server; + private static int httpProxyPort; + + @Test + public void testGetVersions() throws Exception { + doTestGetVersions(""); + doTestGetVersions("Xml"); + } + + @SuppressWarnings("unchecked") + private void doTestGetVersions(String suffix) throws Exception { + // test getVersions doesn't need a body + // assert expected result + Object o = template().requestBody("direct:getVersions" + suffix, (Object) null); + List versions = null; + if (o instanceof Versions) { + versions = ((Versions) o).getVersions(); + } else { + versions = (List) o; + } + assertNotNull(versions); + LOG.debug("Versions: {}", versions); + } + + @BeforeClass + public static void setupServer() throws Exception { + // start a local HTTP proxy using Jetty server + server = new Server(); + + Connector connector = new SelectChannelConnector(); + connector.setHost(HTTP_PROXY_HOST); + server.setConnectors(new Connector[]{connector}); + + final String authenticationString = "Basic " + + B64Code.encode(HTTP_PROXY_USER_NAME + ":" + HTTP_PROXY_PASSWORD, StringUtil.__ISO_8859_1); + + ConnectHandler handler = new ConnectHandler() { + @Override + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException { + // validate proxy-authentication header + final String header = request.getHeader(HttpHeaders.PROXY_AUTHORIZATION); + if (!authenticationString.equals(header)) { + throw new ServletException("Missing header " + HttpHeaders.PROXY_AUTHORIZATION); + } + LOG.info("CONNECT exchange contains required header " + HttpHeaders.PROXY_AUTHORIZATION); + return super.handleAuthentication(request, response, address); + } + }; + server.setHandler(handler); + + LOG.info("Starting proxy server..."); + server.start(); + + httpProxyPort = connector.getLocalPort(); + LOG.info("Started proxy server on port {}", httpProxyPort); + } + + @Override + protected void createComponent() throws Exception { + + super.createComponent(); + final SalesforceComponent salesforce = (SalesforceComponent) context().getComponent("salesforce"); + + // set HTTP proxy settings + salesforce.setHttpProxyHost(HTTP_PROXY_HOST); + salesforce.setHttpProxyPort(httpProxyPort); + salesforce.setHttpProxyUsername(HTTP_PROXY_USER_NAME); + salesforce.setHttpProxyPassword(HTTP_PROXY_PASSWORD); + + // set HTTP client properties + final HashMap properties = new HashMap(); + properties.put("timeout", "60000"); + properties.put("removeIdleDestinations", "true"); + salesforce.setHttpClientProperties(properties); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + AbstractSalesforceTestBase.tearDownAfterClass(); + // stop the proxy server after component + LOG.info("Stopping proxy server..."); + server.stop(); + LOG.info("Stopped proxy server"); + } + + @Override + protected RouteBuilder doCreateRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + + // testGetVersion + from("direct:getVersions") + .to("salesforce:getVersions"); + + // allow overriding format per endpoint + from("direct:getVersionsXml") + .to("salesforce:getVersions?format=XML"); + + } + }; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-maven-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/pom.xml b/components/camel-salesforce/camel-salesforce-maven-plugin/pom.xml index 19aa4d5..3462b5c 100644 --- a/components/camel-salesforce/camel-salesforce-maven-plugin/pom.xml +++ b/components/camel-salesforce/camel-salesforce-maven-plugin/pom.xml @@ -79,6 +79,12 @@ test + org.eclipse.jetty + jetty-server + ${jetty-version} + test + + org.slf4j slf4j-log4j12 ${slf4j-api-version} http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-maven-plugin/src/it/simple-it/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/it/simple-it/pom.xml b/components/camel-salesforce/camel-salesforce-maven-plugin/src/it/simple-it/pom.xml index 0f24037..b025aad 100644 --- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/it/simple-it/pom.xml +++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/it/simple-it/pom.xml @@ -61,6 +61,20 @@ generate + + 60000 + 10 + true + + + + + + SSL.* + + + + (.*__c) ${clientId} http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java index d517f24..372b6e4 100644 --- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java +++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/main/java/org/apache/camel/maven/CamelSalesforceMojo.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Field; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -49,6 +50,9 @@ import org.apache.camel.component.salesforce.internal.SalesforceSession; import org.apache.camel.component.salesforce.internal.client.DefaultRestClient; import org.apache.camel.component.salesforce.internal.client.RestClient; import org.apache.camel.component.salesforce.internal.client.SyncResponseCallback; +import org.apache.camel.util.IntrospectionSupport; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.jsse.SSLContextParameters; import org.apache.log4j.Logger; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -62,105 +66,148 @@ import org.apache.velocity.runtime.RuntimeConstants; import org.apache.velocity.runtime.log.Log4JLogChute; import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; import org.codehaus.jackson.map.ObjectMapper; +import org.eclipse.jetty.client.Address; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.RedirectListener; +import org.eclipse.jetty.client.security.ProxyAuthorization; +import org.eclipse.jetty.util.ssl.SslContextFactory; /** - * Goal which generates POJOs for Salesforce SObjects + * Goal to generate DTOs for Salesforce SObjects */ @Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES) public class CamelSalesforceMojo extends AbstractMojo { + + // default connect and call timeout + protected static final int DEFAULT_TIMEOUT = 60000; + private static final String JAVA_EXT = ".java"; private static final String PACKAGE_NAME_PATTERN = "^[a-z]+(\\.[a-z][a-z0-9]*)*$"; + private static final String SOBJECT_POJO_VM = "/sobject-pojo.vm"; private static final String SOBJECT_QUERY_RECORDS_VM = "/sobject-query-records.vm"; private static final String SOBJECT_PICKLIST_VM = "/sobject-picklist.vm"; // used for velocity logging, to avoid creating velocity.log private static final Logger LOG = Logger.getLogger(CamelSalesforceMojo.class.getName()); - private static final int TIMEOUT = 60000; /** - * Salesforce client id + * HTTP client properties. + */ + @Parameter + protected Map httpClientProperties; + + /** + * SSL Context parameters. + */ + @Parameter(property = "camelSalesforce.sslContextParameters") + protected SSLContextParameters sslContextParameters; + + /** + * HTTP Proxy host. + */ + @Parameter(property = "camelSalesforce.httpProxyHost") + protected String httpProxyHost; + + /** + * HTTP Proxy port. + */ + @Parameter(property = "camelSalesforce.httpProxyPort") + protected Integer httpProxyPort; + + /** + * Proxy authentication username. + */ + @Parameter(property = "camelSalesforce.httpProxyUsername") + protected String httpProxyUsername; + + /** + * Proxy authentication password. + */ + @Parameter(property = "camelSalesforce.httpProxyPassword") + protected String httpProxyPassword; + + /** + * Salesforce client id. */ @Parameter(property = "camelSalesforce.clientId", required = true) protected String clientId; /** - * Salesforce client secret + * Salesforce client secret. */ @Parameter(property = "camelSalesforce.clientSecret", required = true) protected String clientSecret; /** - * Salesforce user name + * Salesforce username. */ @Parameter(property = "camelSalesforce.userName", required = true) protected String userName; /** - * Salesforce password + * Salesforce password. */ @Parameter(property = "camelSalesforce.password", required = true) protected String password; /** - * Salesforce version + * Salesforce API version. */ @Parameter(property = "camelSalesforce.version", defaultValue = SalesforceEndpointConfig.DEFAULT_VERSION) protected String version; /** - * Location of the file. + * Location of generated DTO files, defaults to target/generated-sources/camel-salesforce. */ @Parameter(property = "camelSalesforce.outputDirectory", defaultValue = "${project.build.directory}/generated-sources/camel-salesforce") protected File outputDirectory; /** - * Salesforce URL. + * Salesforce login URL, defaults to https://login.salesforce.com. */ @Parameter(property = "camelSalesforce.loginUrl", defaultValue = SalesforceLoginConfig.DEFAULT_LOGIN_URL) protected String loginUrl; /** - * Names of Salesforce SObject for which POJOs must be generated + * Names of Salesforce SObject for which DTOs must be generated. */ @Parameter protected String[] includes; /** - * Do NOT generate POJOs for these Salesforce SObjects + * Do NOT generate DTOs for these Salesforce SObjects. */ @Parameter protected String[] excludes; /** - * Include Salesforce SObjects that match pattern + * Include Salesforce SObjects that match pattern. */ @Parameter(property = "camelSalesforce.includePattern") protected String includePattern; /** - * Exclude Salesforce SObjects that match pattern + * Exclude Salesforce SObjects that match pattern. */ @Parameter(property = "camelSalesforce.excludePattern") protected String excludePattern; /** - * Java package name for generated POJOs + * Java package name for generated DTOs. */ @Parameter(property = "camelSalesforce.packageName", defaultValue = "org.apache.camel.salesforce.dto") protected String packageName; private VelocityEngine engine; + private long responseTimeout; /** - * Execute the mojo to generate SObject POJOs + * Execute the mojo to generate SObject DTOs * * @throws MojoExecutionException */ - // CHECKSTYLE:OFF public void execute() throws MojoExecutionException { // initialize velocity to load resources from class loader and use Log4J Properties velocityProperties = new Properties(); @@ -177,15 +224,7 @@ public class CamelSalesforceMojo extends AbstractMojo { } // connect to Salesforce - final HttpClient httpClient = new HttpClient(); - httpClient.registerListener(RedirectListener.class.getName()); - httpClient.setConnectTimeout(TIMEOUT); - httpClient.setTimeout(TIMEOUT); - try { - httpClient.start(); - } catch (Exception e) { - throw new MojoExecutionException("Error creating HTTP client: " + e.getMessage(), e); - } + final HttpClient httpClient = createHttpClient(); final SalesforceSession session = new SalesforceSession(httpClient, new SalesforceLoginConfig(loginUrl, clientId, clientSecret, userName, password, false)); @@ -221,7 +260,7 @@ public class CamelSalesforceMojo extends AbstractMojo { try { getLog().info("Getting Salesforce Objects..."); restClient.getGlobalObjects(callback); - if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) { + if (!callback.await(responseTimeout, TimeUnit.MILLISECONDS)) { throw new MojoExecutionException("Timeout waiting for getGlobalObjects!"); } final SalesforceException ex = callback.getException(); @@ -243,67 +282,11 @@ public class CamelSalesforceMojo extends AbstractMojo { // check if we are generating POJOs for all objects or not if ((includes != null && includes.length > 0) || (excludes != null && excludes.length > 0) - || (includePattern != null && !includePattern.trim().isEmpty()) - || (excludePattern != null && !excludePattern.trim().isEmpty())) { - - getLog().info("Looking for matching Object names..."); - // create a list of accepted names - final Set includedNames = new HashSet(); - if (includes != null && includes.length > 0) { - for (String name : includes) { - name = name.trim(); - if (name.isEmpty()) { - throw new MojoExecutionException("Invalid empty name in includes"); - } - includedNames.add(name); - } - } - - final Set excludedNames = new HashSet(); - if (excludes != null && excludes.length > 0) { - for (String name : excludes) { - name = name.trim(); - if (name.isEmpty()) { - throw new MojoExecutionException("Invalid empty name in excludes"); - } - excludedNames.add(name); - } - } + || ObjectHelper.isNotEmpty(includePattern) + || ObjectHelper.isNotEmpty(excludePattern)) { - // check whether a pattern is in effect - Pattern incPattern; - if (includePattern != null && !includePattern.trim().isEmpty()) { - incPattern = Pattern.compile(includePattern.trim()); - } else if (includedNames.isEmpty()) { - // include everything by default if no include names are set - incPattern = Pattern.compile(".*"); - } else { - // include nothing by default if include names are set - incPattern = Pattern.compile("^$"); - } - - // check whether a pattern is in effect - Pattern excPattern; - if (excludePattern != null && !excludePattern.trim().isEmpty()) { - excPattern = Pattern.compile(excludePattern.trim()); - } else { - // exclude nothing by default - excPattern = Pattern.compile("^$"); - } + filterObjectNames(objectNames); - final Set acceptedNames = new HashSet(); - for (String name : objectNames) { - // name is included, or matches include pattern - // and is not excluded and does not match exclude pattern - if ((includedNames.contains(name) || incPattern.matcher(name).matches()) - && !excludedNames.contains(name) && !excPattern.matcher(name).matches()) { - acceptedNames.add(name); - } - } - objectNames.clear(); - objectNames.addAll(acceptedNames); - - getLog().info(String.format("Found %s matching Objects", objectNames.size())); } else { getLog().warn(String.format("Generating Java classes for all %s Objects, this may take a while...", objectNames.size())); } @@ -314,16 +297,16 @@ public class CamelSalesforceMojo extends AbstractMojo { getLog().info("Retrieving Object descriptions..."); for (String name : objectNames) { try { - callback.reset(); - restClient.getDescription(name, callback); - if (!callback.await(TIMEOUT, TimeUnit.MILLISECONDS)) { - throw new MojoExecutionException("Timeout waiting for getDescription for sObject " + name); - } - final SalesforceException ex = callback.getException(); - if (ex != null) { - throw ex; - } - descriptions.add(mapper.readValue(callback.getResponse(), SObjectDescription.class)); + callback.reset(); + restClient.getDescription(name, callback); + if (!callback.await(responseTimeout, TimeUnit.MILLISECONDS)) { + throw new MojoExecutionException("Timeout waiting for getDescription for sObject " + name); + } + final SalesforceException ex = callback.getException(); + if (ex != null) { + throw ex; + } + descriptions.add(mapper.readValue(callback.getResponse(), SObjectDescription.class)); } catch (Exception e) { String msg = "Error getting SObject description for '" + name + "': " + e.getMessage(); throw new MojoExecutionException(msg, e); @@ -372,7 +355,129 @@ public class CamelSalesforceMojo extends AbstractMojo { } } } - // CHECKSTYLE:ON + + protected void filterObjectNames(Set objectNames) throws MojoExecutionException { + getLog().info("Looking for matching Object names..."); + // create a list of accepted names + final Set includedNames = new HashSet(); + if (includes != null && includes.length > 0) { + for (String name : includes) { + name = name.trim(); + if (name.isEmpty()) { + throw new MojoExecutionException("Invalid empty name in includes"); + } + includedNames.add(name); + } + } + + final Set excludedNames = new HashSet(); + if (excludes != null && excludes.length > 0) { + for (String name : excludes) { + name = name.trim(); + if (name.isEmpty()) { + throw new MojoExecutionException("Invalid empty name in excludes"); + } + excludedNames.add(name); + } + } + + // check whether a pattern is in effect + Pattern incPattern; + if (includePattern != null && !includePattern.trim().isEmpty()) { + incPattern = Pattern.compile(includePattern.trim()); + } else if (includedNames.isEmpty()) { + // include everything by default if no include names are set + incPattern = Pattern.compile(".*"); + } else { + // include nothing by default if include names are set + incPattern = Pattern.compile("^$"); + } + + // check whether a pattern is in effect + Pattern excPattern; + if (excludePattern != null && !excludePattern.trim().isEmpty()) { + excPattern = Pattern.compile(excludePattern.trim()); + } else { + // exclude nothing by default + excPattern = Pattern.compile("^$"); + } + + final Set acceptedNames = new HashSet(); + for (String name : objectNames) { + // name is included, or matches include pattern + // and is not excluded and does not match exclude pattern + if ((includedNames.contains(name) || incPattern.matcher(name).matches()) + && !excludedNames.contains(name) && !excPattern.matcher(name).matches()) { + acceptedNames.add(name); + } + } + objectNames.clear(); + objectNames.addAll(acceptedNames); + + getLog().info(String.format("Found %s matching Objects", objectNames.size())); + } + + protected HttpClient createHttpClient() throws MojoExecutionException { + + final HttpClient httpClient = new HttpClient(); + + // default settings + httpClient.registerListener(RedirectListener.class.getName()); + httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL); + httpClient.setConnectTimeout(DEFAULT_TIMEOUT); + httpClient.setTimeout(DEFAULT_TIMEOUT); + + // set ssl context parameters + try { + final SSLContextParameters contextParameters = sslContextParameters != null + ? sslContextParameters : new SSLContextParameters(); + final SslContextFactory sslContextFactory = httpClient.getSslContextFactory(); + sslContextFactory.setSslContext(contextParameters.createSSLContext()); + } catch (GeneralSecurityException e) { + throw new MojoExecutionException("Error creating default SSL context: " + e.getMessage(), e); + } catch (IOException e) { + throw new MojoExecutionException("Error creating default SSL context: " + e.getMessage(), e); + } + + // set HTTP client parameters + if (httpClientProperties != null && !httpClientProperties.isEmpty()) { + try { + IntrospectionSupport.setProperties(httpClient, new HashMap(httpClientProperties)); + } catch (Exception e) { + throw new MojoExecutionException("Error setting HTTP client properties: " + e.getMessage(), e); + } + } + + // wait for 1 second longer than the HTTP client response timeout + responseTimeout = httpClient.getTimeout() + 1000L; + + // set http proxy settings + if (this.httpProxyHost != null && httpProxyPort != null) { + httpClient.setProxy(new Address(this.httpProxyHost, this.httpProxyPort)); + } + if (this.httpProxyUsername != null && httpProxyPassword != null) { + try { + httpClient.setProxyAuthentication(new ProxyAuthorization(this.httpProxyUsername, this.httpProxyPassword)); + } catch (IOException e) { + throw new MojoExecutionException("Error configuring proxy authorization: " + e.getMessage(), e); + } + } + + // add redirect listener to handle Salesforce redirects + // this is ok to do since the RedirectListener is in the same classloader as Jetty client + String listenerClass = RedirectListener.class.getName(); + if (httpClient.getRegisteredListeners() == null + || !httpClient.getRegisteredListeners().contains(listenerClass)) { + httpClient.registerListener(listenerClass); + } + + try { + httpClient.start(); + } catch (Exception e) { + throw new MojoExecutionException("Error creating HTTP client: " + e.getMessage(), e); + } + return httpClient; + } private void processDescription(File pkgDir, SObjectDescription description, GeneratorUtility utility, String generatedDate) throws MojoExecutionException { // generate a source file for SObject http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java index cf9bcb9..a40b9b9 100644 --- a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java +++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/CamelSalesforceMojoIntegrationTest.java @@ -35,6 +35,19 @@ public class CamelSalesforceMojoIntegrationTest { @Test public void testExecute() throws Exception { + CamelSalesforceMojo mojo = createMojo(); + + // generate code + mojo.execute(); + + // validate generated code + // check that it was generated + Assert.assertTrue("Output directory was not created", mojo.outputDirectory.exists()); + + // TODO check that the generated code compiles + } + + protected CamelSalesforceMojo createMojo() throws IOException { CamelSalesforceMojo mojo = new CamelSalesforceMojo(); mojo.setLog(new SystemStreamLog()); @@ -59,15 +72,7 @@ public class CamelSalesforceMojoIntegrationTest { } mojo.outputDirectory.delete(); } - - // generate code - mojo.execute(); - - // validate generated code - // check that it was generated - Assert.assertTrue("Output directory was not created", mojo.outputDirectory.exists()); - - // TODO check that the generated code compiles + return mojo; } private void setLoginProperties(CamelSalesforceMojo mojo) throws IOException { http://git-wip-us.apache.org/repos/asf/camel/blob/ede4a72b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/HttpProxyMojoIntegrationTest.java ---------------------------------------------------------------------- diff --git a/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/HttpProxyMojoIntegrationTest.java b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/HttpProxyMojoIntegrationTest.java new file mode 100644 index 0000000..47d15fb --- /dev/null +++ b/components/camel-salesforce/camel-salesforce-maven-plugin/src/test/java/org/apache/camel/maven/HttpProxyMojoIntegrationTest.java @@ -0,0 +1,110 @@ +/** + * 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.maven; + +import java.io.IOException; +import java.util.HashMap; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.camel.util.jsse.SSLContextParameters; +import org.eclipse.jetty.http.HttpHeaders; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ConnectHandler; +import org.eclipse.jetty.server.nio.SelectChannelConnector; +import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HttpProxyMojoIntegrationTest extends CamelSalesforceMojoIntegrationTest { + + private static final Logger LOG = LoggerFactory.getLogger(HttpProxyMojoIntegrationTest.class); + + private static final String HTTP_PROXY_HOST = "localhost"; + private static final String HTTP_PROXY_USER_NAME = "camel-user"; + private static final String HTTP_PROXY_PASSWORD = "camel-user-password"; + + private static Server server; + private static int httpProxyPort; + + @BeforeClass + public static void setupServer() throws Exception { + // start a local HTTP proxy using Jetty server + server = new Server(); + + Connector connector = new SelectChannelConnector(); + connector.setHost(HTTP_PROXY_HOST); + server.setConnectors(new Connector[]{connector}); + + final String authenticationString = "Basic " + + B64Code.encode(HTTP_PROXY_USER_NAME + ":" + HTTP_PROXY_PASSWORD, StringUtil.__ISO_8859_1); + + ConnectHandler handler = new ConnectHandler() { + @Override + protected boolean handleAuthentication(HttpServletRequest request, HttpServletResponse response, String address) throws ServletException, IOException { + // validate proxy-authentication header + final String header = request.getHeader(HttpHeaders.PROXY_AUTHORIZATION); + if (!authenticationString.equals(header)) { + throw new ServletException("Missing header " + HttpHeaders.PROXY_AUTHORIZATION); + } + LOG.info("CONNECT exchange contains required header " + HttpHeaders.PROXY_AUTHORIZATION); + return super.handleAuthentication(request, response, address); + } + }; + server.setHandler(handler); + + LOG.info("Starting proxy server..."); + server.start(); + + httpProxyPort = connector.getLocalPort(); + LOG.info("Started proxy server on port {}", httpProxyPort); + } + + @Override + protected CamelSalesforceMojo createMojo() throws IOException { + final CamelSalesforceMojo mojo = super.createMojo(); + + // SSL context parameters + mojo.sslContextParameters = new SSLContextParameters(); + + // HTTP proxy properties + mojo.httpProxyHost = HTTP_PROXY_HOST; + mojo.httpProxyPort = httpProxyPort; + mojo.httpProxyUsername = HTTP_PROXY_USER_NAME; + mojo.httpProxyPassword = HTTP_PROXY_PASSWORD; + + // HTTP client properties + mojo.httpClientProperties = new HashMap(); + mojo.httpClientProperties.put("timeout", "60000"); + mojo.httpClientProperties.put("removeIdleDestinations", "true"); + + return mojo; + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + // stop the proxy server after component + LOG.info("Stopping proxy server..."); + server.stop(); + LOG.info("Stopped proxy server"); + } +}