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 5ADE71157F for ; Fri, 27 Jun 2014 23:22:20 +0000 (UTC) Received: (qmail 80517 invoked by uid 500); 27 Jun 2014 23:22:20 -0000 Delivered-To: apmail-camel-commits-archive@camel.apache.org Received: (qmail 80372 invoked by uid 500); 27 Jun 2014 23:22: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 80061 invoked by uid 99); 27 Jun 2014 23:22:20 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 27 Jun 2014 23:22:20 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id DB80732B207; Fri, 27 Jun 2014 23:22:19 +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 Date: Fri, 27 Jun 2014 23:22:23 -0000 Message-Id: <724846e9e6c64c4da15fc8c408df9c06@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [5/5] git commit: Intial version of Camel support for Box.com, it's also a POC for the awesome Camel API component framework Intial version of Camel support for Box.com, it's also a POC for the awesome Camel API component framework Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/a4c8f76c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/a4c8f76c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/a4c8f76c Branch: refs/heads/master Commit: a4c8f76ca7f0299d7f50a05a11d5c2f7a88b6b83 Parents: 6fccf69 Author: Dhiraj Bokde Authored: Fri Jun 27 16:21:39 2014 -0700 Committer: Dhiraj Bokde Committed: Fri Jun 27 16:22:04 2014 -0700 ---------------------------------------------------------------------- components/camel-box/pom.xml | 414 +++++++++++++++++ .../camel/component/box/BoxComponent.java | 87 ++++ .../camel/component/box/BoxConfiguration.java | 199 ++++++++ .../apache/camel/component/box/BoxConsumer.java | 32 ++ .../apache/camel/component/box/BoxEndpoint.java | 230 ++++++++++ .../apache/camel/component/box/BoxProducer.java | 36 ++ .../component/box/internal/BoxClientHelper.java | 220 +++++++++ .../component/box/internal/BoxConstants.java | 29 ++ .../box/internal/BoxPropertiesHelper.java | 39 ++ .../component/box/internal/CachedBoxClient.java | 72 +++ .../box/internal/CachingSecureStorage.java | 50 +++ .../box/internal/LoginAuthFlowListener.java | 67 +++ .../component/box/internal/LoginAuthFlowUI.java | 190 ++++++++ .../box/internal/OAuthHelperListener.java | 53 +++ .../services/org/apache/camel/component/box | 18 + .../component/box/AbstractBoxTestSupport.java | 165 +++++++ ...BoxCollaborationsManagerIntegrationTest.java | 154 +++++++ .../box/IBoxCommentsManagerIntegrationTest.java | 143 ++++++ .../box/IBoxEventsManagerIntegrationTest.java | 77 ++++ .../box/IBoxFilesManagerIntegrationTest.java | 449 +++++++++++++++++++ .../box/IBoxFoldersManagerIntegrationTest.java | 236 ++++++++++ .../box/IBoxGroupsManagerIntegrationTest.java | 326 ++++++++++++++ .../box/IBoxSearchManagerIntegrationTest.java | 66 +++ .../IBoxSharedItemsManagerIntegrationTest.java | 55 +++ .../box/IBoxUsersManagerIntegrationTest.java | 280 ++++++++++++ .../src/test/resources/log4j.properties | 36 ++ .../src/test/resources/test-options.properties | 45 ++ components/pom.xml | 1 + 28 files changed, 3769 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-box/pom.xml b/components/camel-box/pom.xml new file mode 100644 index 0000000..e073959 --- /dev/null +++ b/components/camel-box/pom.xml @@ -0,0 +1,414 @@ + + + + 4.0.0 + + + org.apache.camel + components + 2.14-SNAPSHOT + + + camel-box + bundle + Camel Box.com Component + Camel Component for Box.com + + + box + Box + org.apache.camel.component.box + org.apache.camel.component.box.internal + 3.0.9 + 2.15 + + ${componentPackage} + org.apache.camel.spi.ComponentResolver;component=box + 4.3.3 + 1.7 + + + + + org.apache.camel + camel-core + + + net.box + boxjavalibv2 + ${boxjavalibv2.version} + + + org.apache.httpcomponents + httpclient + + + org.apache.httpcomponents + httpclient-cache + + + + + net.sourceforge.htmlunit + htmlunit + ${htmlunit.version} + + + + org.apache.httpcomponents + httpclient-cache + ${httpclient.version} + + + + + org.apache.camel + spi-annotations + ${project.version} + provided + + + + + net.box + boxjavalibv2 + ${boxjavalibv2.version} + javadoc + provided + + + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-log4j12 + test + + + log4j + log4j + test + + + + + org.apache.camel + camel-test + test + + + + + + + + + org.apache.camel + camel-api-component-maven-plugin + + + generate-test-component-classes + + fromApis + + + + + collaborations + com.box.boxjavalibv2.resourcemanagers.IBoxCollaborationsManager + + + + comments + com.box.boxjavalibv2.resourcemanagers.IBoxCommentsManager + + + + events + com.box.boxjavalibv2.resourcemanagers.IBoxEventsManager + + + + files + com.box.boxjavalibv2.resourcemanagers.IBoxFilesManager + + + ^.+$ + ^.+$ + ^\S+\.Box(.+)RequestObject + $1Request + true + + + ^.+$ + type + com.box.boxjavalibv2.dao.BoxResourceType + resourceType + + + ^.+$ + ^id$ + fileId + + + + + + [gs]et(.+) + $1 + + + (create|update|upload|download)\\w+ + $1 + + + + + folders + com.box.boxjavalibv2.resourcemanagers.IBoxFoldersManager + + + ^.+$ + ^.+$ + ^\S+\.Box(.+)RequestObject + $1Request + true + + + ^.+$ + type + com.box.boxjavalibv2.dao.BoxResourceType + resourceType + + + ^.+$ + ^id$ + folderId + + + + + + groups + com.box.boxjavalibv2.resourcemanagers.IBoxGroupsManager + + + ^.+$ + ^.+$ + ^\S+\.Box(.+)RequestObject + $1Request + true + + + ^.+$ + type + com.box.boxjavalibv2.dao.BoxResourceType + resourceType + + + ^.+$ + ^id$ + groupId + + + + + + [gs]et(.+) + $1 + + + (delete|update)\\w+ + $1 + + + + + search + com.box.boxjavalibv2.resourcemanagers.IBoxSearchManager + + + + shared-comments + com.box.boxjavalibv2.resourcemanagers.IBoxCommentsManager + + + shared-files + com.box.boxjavalibv2.resourcemanagers.IBoxFilesManager + + + [gs]et(.+) + $1 + + + (create|update|upload|download)\\w+ + $1 + + + + + shared-folders + com.box.boxjavalibv2.resourcemanagers.IBoxFoldersManager + + + shared-items + com.box.boxjavalibv2.resourcemanagers.IBoxSharedItemsManager + + + + users + com.box.boxjavalibv2.resourcemanagers.IBoxUsersManager + + + [gs]et(.+) + $1 + + + (create|update|upload|download)\\w+ + $1 + + + + + + + + + ^.+$ + ^.+$ + ^\S+\.Box(.+)RequestObject + $1Request + true + + + ^.+$ + type + com.box.boxjavalibv2.dao.BoxResourceType + resourceType + + + + + BoxResourceManager|BoxItemsManager + + + + [gs]et(.+) + $1 + + + (create|delete|update)\\w+ + $1 + + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 1.8 + + + add-generated-sources + + add-source + + + + ${project.build.directory}/generated-sources/camel-component + + + + + add-generated-test-sources + + add-test-source + + + + ${project.build.directory}/generated-test-sources/camel-component + + + + + + + + + + + + org.apache.camel + camel-api-component-maven-plugin + ${project.version} + + ${schemeName} + ${componentName} + ${componentPackage} + ${outPackage} + + + + + + + + + + + org.apache.camel + camel-api-component-maven-plugin + ${project.version} + + ${schemeName} + ${componentName} + ${componentPackage} + ${outPackage} + + + + + + + + box-test + + + + maven-surefire-plugin + + false + true + once + 300 + + **/*Test.java + + + + + + + + + http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/BoxComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/BoxComponent.java b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxComponent.java new file mode 100644 index 0000000..aa48337 --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxComponent.java @@ -0,0 +1,87 @@ +/** + * 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.box; + +import org.apache.camel.CamelContext; +import org.apache.camel.Endpoint; +import org.apache.camel.component.box.internal.BoxApiCollection; +import org.apache.camel.component.box.internal.BoxApiName; +import org.apache.camel.component.box.internal.BoxClientHelper; +import org.apache.camel.component.box.internal.CachedBoxClient; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.util.component.AbstractApiComponent; + +/** + * Represents the component that manages {@link BoxEndpoint}. + */ +@UriEndpoint(scheme = "box", consumerClass = BoxConsumer.class, consumerPrefix = "consumer") +public class BoxComponent extends AbstractApiComponent { + + private CachedBoxClient cachedBoxClient; + + public BoxComponent() { + super(BoxEndpoint.class, BoxApiName.class, BoxApiCollection.getCollection()); + } + + public BoxComponent(CamelContext context) { + super(context, BoxEndpoint.class, BoxApiName.class, BoxApiCollection.getCollection()); + } + + @Override + protected BoxApiName getApiName(String apiNameStr) throws IllegalArgumentException { + return BoxApiName.fromValue(apiNameStr); + } + + @Override + protected Endpoint createEndpoint(String uri, String methodName, BoxApiName apiName, + BoxConfiguration endpointConfiguration) { + return new BoxEndpoint(uri, this, apiName, methodName, endpointConfiguration); + } + + // get the component's singleton BoxClient + protected synchronized CachedBoxClient getBoxClient() { + if (cachedBoxClient == null) { + if (configuration != null) { + cachedBoxClient = BoxClientHelper.createBoxClient(configuration); + } else { + throw new IllegalArgumentException("Unable to connect, Box component configuration is missing"); + } + } + return cachedBoxClient; + } + + @Override + protected void doStop() throws Exception { + if (cachedBoxClient != null) { + // close shared client connections + BoxClientHelper.closeIdleConnections(cachedBoxClient); + } + } + + @Override + public void doShutdown() throws Exception { + try { + if (cachedBoxClient != null) { + // shutdown singleton client + BoxClientHelper.shutdownBoxClient(configuration, cachedBoxClient); + } + } finally { + cachedBoxClient = null; + super.doShutdown(); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConfiguration.java b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConfiguration.java new file mode 100644 index 0000000..0fdac6d --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConfiguration.java @@ -0,0 +1,199 @@ +/** + * 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.box; + +import java.util.Map; + +import com.box.boxjavalibv2.BoxConnectionManagerBuilder; +import com.box.boxjavalibv2.IBoxConfig; +import com.box.boxjavalibv2.authorization.IAuthSecureStorage; +import com.box.boxjavalibv2.authorization.OAuthRefreshListener; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; + +/** + * Component configuration for Box component. + */ +@UriParams +public class BoxConfiguration { + + @UriParam + private String clientId; + + @UriParam + private String clientSecret; + + @UriParam + private IAuthSecureStorage authSecureStorage; + + @UriParam + private String userName; + + @UriParam + private String userPassword; + + @UriParam + private OAuthRefreshListener refreshListener; + + @UriParam + private boolean revokeOnShutdown; + + @UriParam + private String sharedLink; + + @UriParam + private String sharedPassword; + + @UriParam + private IBoxConfig boxConfig; + + @UriParam + private BoxConnectionManagerBuilder connectionManagerBuilder; + + @UriParam + private Map httpParams; + + /** + * Box.com login timeout in seconds, defaults to 30. + */ + @UriParam + private int loginTimeout = 30; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public IAuthSecureStorage getAuthSecureStorage() { + return authSecureStorage; + } + + public void setAuthSecureStorage(IAuthSecureStorage authSecureStorage) { + this.authSecureStorage = authSecureStorage; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getUserPassword() { + return userPassword; + } + + public void setUserPassword(String userPassword) { + this.userPassword = userPassword; + } + + public OAuthRefreshListener getRefreshListener() { + return refreshListener; + } + + public void setRefreshListener(OAuthRefreshListener refreshListener) { + this.refreshListener = refreshListener; + } + + public boolean isRevokeOnShutdown() { + return revokeOnShutdown; + } + + public void setRevokeOnShutdown(boolean revokeOnShutdown) { + this.revokeOnShutdown = revokeOnShutdown; + } + + public String getSharedLink() { + return sharedLink; + } + + public void setSharedLink(String sharedLink) { + this.sharedLink = sharedLink; + } + + public String getSharedPassword() { + return sharedPassword; + } + + public void setSharedPassword(String sharedPassword) { + this.sharedPassword = sharedPassword; + } + + public IBoxConfig getBoxConfig() { + return boxConfig; + } + + public void setBoxConfig(IBoxConfig boxConfig) { + this.boxConfig = boxConfig; + } + + public BoxConnectionManagerBuilder getConnectionManagerBuilder() { + return connectionManagerBuilder; + } + + public void setConnectionManagerBuilder(BoxConnectionManagerBuilder connectionManagerBuilder) { + this.connectionManagerBuilder = connectionManagerBuilder; + } + + public Map getHttpParams() { + return httpParams; + } + + public void setHttpParams(Map httpParams) { + this.httpParams = httpParams; + } + + public int getLoginTimeout() { + return loginTimeout; + } + + public void setLoginTimeout(int loginTimeout) { + this.loginTimeout = loginTimeout; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof BoxConfiguration) { + final BoxConfiguration other = (BoxConfiguration) obj; + // configurations are equal if BoxClient creation parameters are equal + return boxConfig == other.boxConfig + && connectionManagerBuilder == other.connectionManagerBuilder + && httpParams == other.httpParams + && clientId == other.clientId + && clientSecret == other.clientSecret + && authSecureStorage == other.authSecureStorage; + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConsumer.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConsumer.java b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConsumer.java new file mode 100644 index 0000000..3c2679f --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxConsumer.java @@ -0,0 +1,32 @@ +/** + * 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.box; + +import org.apache.camel.Processor; +import org.apache.camel.component.box.internal.BoxApiName; +import org.apache.camel.util.component.AbstractApiConsumer; + +/** + * The Box consumer. + */ +public class BoxConsumer extends AbstractApiConsumer { + + public BoxConsumer(BoxEndpoint endpoint, Processor processor) { + super(endpoint, processor); + } + +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/BoxEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/BoxEndpoint.java b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxEndpoint.java new file mode 100644 index 0000000..d6f5ade --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxEndpoint.java @@ -0,0 +1,230 @@ +/** + * 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.box; + +import java.util.Map; + +import com.box.boxjavalibv2.BoxClient; +import com.box.boxjavalibv2.resourcemanagers.IBoxResourceManager; +import org.apache.camel.Consumer; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.component.box.internal.BoxApiCollection; +import org.apache.camel.component.box.internal.BoxApiName; +import org.apache.camel.component.box.internal.BoxClientHelper; +import org.apache.camel.component.box.internal.BoxPropertiesHelper; +import org.apache.camel.component.box.internal.CachedBoxClient; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.component.AbstractApiEndpoint; +import org.apache.camel.util.component.ApiMethod; +import org.apache.camel.util.component.ApiMethodPropertiesHelper; + +/** + * Represents a Box endpoint. + */ +@UriEndpoint(scheme = "box", consumerClass = BoxConsumer.class, consumerPrefix = "consumer") +public class BoxEndpoint extends AbstractApiEndpoint { + + private static final String SHARED_LINK_PROPERTY = "sharedLink"; + private static final String SHARED_PASSWORD_PROPERTY = "sharedPassword"; + + // cached client + private CachedBoxClient cachedBoxClient; + + // proxy manager + private IBoxResourceManager apiProxy; + + // configuration values for shared links + private String sharedLink; + private String sharedPassword; + + private boolean boxClientShared; + + public BoxEndpoint(String uri, BoxComponent component, + BoxApiName apiName, String methodName, BoxConfiguration endpointConfiguration) { + super(uri, component, apiName, methodName, BoxApiCollection.getCollection().getHelper(apiName), endpointConfiguration); + } + + public Producer createProducer() throws Exception { + return new BoxProducer(this); + } + + public Consumer createConsumer(Processor processor) throws Exception { + // make sure inBody is not set for consumers + if (inBody != null) { + throw new IllegalArgumentException("Option inBody is not supported for consumer endpoint"); + } + final BoxConsumer consumer = new BoxConsumer(this, processor); + // also set consumer.* properties + configureConsumer(consumer); + return consumer; + } + + @Override + protected ApiMethodPropertiesHelper getPropertiesHelper() { + return BoxPropertiesHelper.getHelper(); + } + + @Override + protected void afterConfigureProperties() { + // create client eagerly, a good way to validate configuration + createBoxClient(); + + this.sharedLink = configuration.getSharedLink(); + this.sharedPassword = configuration.getSharedPassword(); + + // validate shared endpoints + switch (getApiName()) { + case SHARED_COMMENTS: + case SHARED_FILES: + case SHARED_FOLDERS: + case SHARED_ITEMS: + if (ObjectHelper.isEmpty(sharedLink)) { + log.warn("Header properties sharedLink and sharedPassword MUST be provided for endpoint {}", + getEndpointUri()); + } + break; + default: + } + } + + private void createBoxClient() { + final BoxComponent component = getComponent(); + this.boxClientShared = configuration.equals(getComponent().getConfiguration()); + if (boxClientShared) { + // get shared singleton client from Component + cachedBoxClient = component.getBoxClient(); + } else { + cachedBoxClient = BoxClientHelper.createBoxClient(configuration); + } + } + + @Override + public BoxComponent getComponent() { + return (BoxComponent) super.getComponent(); + } + + @Override + protected void interceptProperties(Map properties) { + // set shared link and password from configuration if not set as header properties + if (!properties.containsKey(SHARED_LINK_PROPERTY) && !ObjectHelper.isEmpty(sharedLink)) { + properties.put(SHARED_LINK_PROPERTY, sharedLink); + } + if (!properties.containsKey(SHARED_PASSWORD_PROPERTY) && !ObjectHelper.isEmpty(sharedPassword)) { + properties.put(SHARED_PASSWORD_PROPERTY, sharedPassword); + } + } + + @Override + public Object getApiProxy(ApiMethod method, Map args) { + if (apiProxy == null) { + // create API proxy lazily + createApiProxy(args); + } + return apiProxy; + } + + private void createApiProxy(Map args) { + + // get shared link and password from args + final String sharedLink = (String) args.get("sharedLink"); + final String sharedPassword = (String) args.get("sharedPassword"); + + switch (apiName) { + case SHARED_COMMENTS: + case SHARED_FILES: + case SHARED_FOLDERS: + case SHARED_ITEMS: + if (ObjectHelper.isEmpty(sharedLink)) { + throw new IllegalArgumentException("Missing required property sharedLink"); + } + default: + } + + final BoxClient boxClient = cachedBoxClient.getBoxClient(); + switch (apiName) { + case COLLABORATIONS: + apiProxy = boxClient.getCollaborationsManager(); + break; + case COMMENTS: + apiProxy = boxClient.getCommentsManager(); + break; + case EVENTS: + apiProxy = boxClient.getEventsManager(); + break; + case FILES: + apiProxy = boxClient.getFilesManager(); + break; + case FOLDERS: + apiProxy = boxClient.getFoldersManager(); + break; + case GROUPS: + apiProxy = boxClient.getGroupsManager(); + break; + case SEARCH: + apiProxy = boxClient.getSearchManager(); + break; + case SHARED_FILES: + apiProxy = boxClient.getSharedFilesManager(sharedLink, sharedPassword); + break; + case SHARED_FOLDERS: + apiProxy = boxClient.getSharedFoldersManager(sharedLink, sharedPassword); + break; + case SHARED_COMMENTS: + apiProxy = boxClient.getSharedCommentsManager(sharedLink, sharedPassword); + break; + case SHARED_ITEMS: + apiProxy = boxClient.getSharedItemsManager(sharedLink, sharedPassword); + break; + case USERS: + apiProxy = boxClient.getUsersManager(); + break; + default: + } + } + + @Override + protected void doStart() throws Exception { + BoxClientHelper.getOAuthToken(configuration, cachedBoxClient); + } + + @Override + protected void doStop() throws Exception { + try { + if (!boxClientShared) { + // while there is no way to suspend BoxClient, we can close idle connections to be nice + BoxClientHelper.closeIdleConnections(cachedBoxClient); + } + } finally { + super.doStop(); + } + } + + @Override + public void doShutdown() throws Exception { + try { + // cleanup if BoxClient is not shared + if (!boxClientShared) { + BoxClientHelper.shutdownBoxClient(configuration, cachedBoxClient); + } + } finally { + cachedBoxClient = null; + super.doShutdown(); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/BoxProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/BoxProducer.java b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxProducer.java new file mode 100644 index 0000000..a1d2cf3 --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/BoxProducer.java @@ -0,0 +1,36 @@ +/** + * 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.box; + +import org.apache.camel.component.box.internal.BoxApiName; +import org.apache.camel.component.box.internal.BoxConstants; +import org.apache.camel.component.box.internal.BoxPropertiesHelper; +import org.apache.camel.util.component.AbstractApiProducer; + +/** + * The Box producer. + */ +public class BoxProducer extends AbstractApiProducer { + + public BoxProducer(BoxEndpoint endpoint) { + super(endpoint, BoxPropertiesHelper.getHelper()); + } + + protected String getThreadProfileName() { + return BoxConstants.THREAD_PROFILE_NAME; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxClientHelper.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxClientHelper.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxClientHelper.java new file mode 100644 index 0000000..621132f --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxClientHelper.java @@ -0,0 +1,220 @@ +/** + * 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.box.internal; + +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.box.boxjavalibv2.BoxClient; +import com.box.boxjavalibv2.BoxConnectionManagerBuilder; +import com.box.boxjavalibv2.BoxRESTClient; +import com.box.boxjavalibv2.authorization.IAuthFlowUI; +import com.box.boxjavalibv2.authorization.IAuthSecureStorage; +import com.box.boxjavalibv2.exceptions.AuthFatalFailureException; +import com.box.boxjavalibv2.exceptions.BoxServerException; +import com.box.restclientv2.IBoxRESTClient; +import com.box.restclientv2.exceptions.BoxRestException; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.box.BoxConfiguration; +import org.apache.camel.util.ObjectHelper; +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.params.HttpParams; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class to work with {@link BoxClient}. + */ +public final class BoxClientHelper { + + private static final Logger LOG = LoggerFactory.getLogger(BoxClientHelper.class); + + private BoxClientHelper() { + } + + // create BoxClient using provided configuration + @SuppressWarnings("deprecation") + public static CachedBoxClient createBoxClient(final BoxConfiguration configuration) { + + final String clientId = configuration.getClientId(); + final String clientSecret = configuration.getClientSecret(); + + final IAuthSecureStorage authSecureStorage = configuration.getAuthSecureStorage(); + final String userName = configuration.getUserName(); + final String userPassword = configuration.getUserPassword(); + + if ((authSecureStorage == null && ObjectHelper.isEmpty(userPassword)) + || ObjectHelper.isEmpty(userName) || ObjectHelper.isEmpty(clientId) || ObjectHelper.isEmpty(clientSecret)) { + throw new IllegalArgumentException( + "Missing one or more required properties " + + "clientId, clientSecret, userName and either authSecureStorage or userPassword"); + } + LOG.debug("Creating BoxClient for login:{}, client_id:{} ...", userName, clientId); + + // if set, use configured connection manager builder + final BoxConnectionManagerBuilder configBuilder = configuration.getConnectionManagerBuilder(); + final BoxConnectionManagerBuilder connectionManager = configBuilder != null + ? configBuilder : new BoxConnectionManagerBuilder(); + + // create REST client for BoxClient + final ClientConnectionManager[] clientConnectionManager = new ClientConnectionManager[1]; + final IBoxRESTClient restClient = new BoxRESTClient(connectionManager.build()) { + @Override + public HttpClient getRawHttpClient() { + final HttpClient httpClient = super.getRawHttpClient(); + clientConnectionManager[0] = httpClient.getConnectionManager(); + + // set custom HTTP params + final Map configParams = configuration.getHttpParams(); + if (configParams != null && !configParams.isEmpty()) { + LOG.debug("Setting {} HTTP Params", configParams.size()); + + final HttpParams httpParams = httpClient.getParams(); + for (Map.Entry param : configParams.entrySet()) { + httpParams.setParameter(param.getKey(), param.getValue()); + } + } + + return httpClient; + } + }; + final BoxClient boxClient = new BoxClient(clientId, clientSecret, null, null, + restClient, configuration.getBoxConfig()); + + // enable OAuth auto-refresh + boxClient.setAutoRefreshOAuth(true); + + // wrap the configured storage in a caching storage + final CachingSecureStorage storage = new CachingSecureStorage(authSecureStorage); + + // set up a listener to notify secure storage and user provided listener, store it in configuration! + final OAuthHelperListener listener = new OAuthHelperListener(storage, configuration.getRefreshListener()); + boxClient.addOAuthRefreshListener(listener); + + final CachedBoxClient cachedBoxClient = new CachedBoxClient(boxClient, userName, clientId, storage, listener, clientConnectionManager); + LOG.debug("BoxClient created {}", cachedBoxClient); + return cachedBoxClient; + } + + public static void getOAuthToken(BoxConfiguration configuration, CachedBoxClient cachedBoxClient) + throws AuthFatalFailureException, BoxRestException, BoxServerException, InterruptedException { + + final BoxClient boxClient = cachedBoxClient.getBoxClient(); + synchronized (boxClient) { + if (boxClient.isAuthenticated()) { + return; + } + + LOG.debug("Getting OAuth token for {}...", cachedBoxClient); + + final IAuthSecureStorage authSecureStorage = cachedBoxClient.getSecureStorage(); + if (authSecureStorage != null && authSecureStorage.getAuth() != null) { + + LOG.debug("Using secure storage for {}", cachedBoxClient); + // authenticate using stored refresh token + boxClient.authenticateFromSecureStorage(authSecureStorage); + } else { + + LOG.debug("Using OAuth {}", cachedBoxClient); + // authorize App for user, and create OAuth token with refresh token + final IAuthFlowUI authFlowUI = new LoginAuthFlowUI(configuration, boxClient); + final CountDownLatch latch = new CountDownLatch(1); + final LoginAuthFlowListener listener = new LoginAuthFlowListener(latch); + boxClient.authenticate(authFlowUI, true, listener); + + // wait for login to finish or timeout + if (!latch.await(configuration.getLoginTimeout(), TimeUnit.SECONDS)) { + if (!boxClient.isAuthenticated()) { + throw new RuntimeCamelException(String.format("Login timeout for %s", cachedBoxClient)); + } + } + final Exception ex = listener.getException(); + if (ex != null) { + throw new RuntimeCamelException(String.format("Login error for %s: %s", + cachedBoxClient, ex.getMessage()), ex); + } + } + + LOG.debug("OAuth token created for {}", cachedBoxClient); + // notify the cached client listener for the first time, since BoxClient doesn't!!! + cachedBoxClient.getListener().onRefresh(boxClient.getAuthData()); + } + } + + public static void closeIdleConnections(CachedBoxClient cachedBoxClient) { + @SuppressWarnings("deprecation") + final ClientConnectionManager connectionManager = cachedBoxClient.getClientConnectionManager(); + if (connectionManager != null) { + // close all idle connections + connectionManager.closeIdleConnections(1, TimeUnit.MILLISECONDS); + } + } + + public static void shutdownBoxClient(BoxConfiguration configuration, CachedBoxClient cachedBoxClient) + throws BoxServerException, BoxRestException, AuthFatalFailureException { + + final BoxClient boxClient = cachedBoxClient.getBoxClient(); + synchronized (boxClient) { + + LOG.debug("Shutting down {} ...", cachedBoxClient); + try { + // revoke token if requested + if (configuration.isRevokeOnShutdown()) { + revokeOAuthToken(configuration, cachedBoxClient); + } + } finally { + + boxClient.setConnectionOpen(false); + // close connections in the underlying HttpClient + @SuppressWarnings("deprecation") + final ClientConnectionManager connectionManager = cachedBoxClient.getClientConnectionManager(); + if (connectionManager != null) { + LOG.debug("Closing connections for {}", cachedBoxClient); + + connectionManager.shutdown(); + } else { + LOG.debug("ConnectionManager not created for {}", cachedBoxClient); + } + } + LOG.debug("Shutdown successful for {}", cachedBoxClient); + } + } + + private static void revokeOAuthToken(BoxConfiguration configuration, CachedBoxClient cachedBoxClient) + throws BoxServerException, BoxRestException, AuthFatalFailureException { + + final BoxClient boxClient = cachedBoxClient.getBoxClient(); + synchronized (boxClient) { + + if (boxClient.isAuthenticated()) { + + LOG.debug("Revoking OAuth refresh token for {}", cachedBoxClient); + + // notify the OAuthListener of revoked token + cachedBoxClient.getListener().onRefresh(null); + // mark auth data revoked + boxClient.getOAuthDataController().setOAuthData(null); + + // revoke OAuth token + boxClient.getOAuthManager().revokeOAuth(boxClient.getAuthData().getAccessToken(), + configuration.getClientId(), configuration.getClientSecret()); + } + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java new file mode 100644 index 0000000..11f584b --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxConstants.java @@ -0,0 +1,29 @@ +/** + * 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.box.internal; + +/** + * Constants for Box component. + */ +public interface BoxConstants { + + // suffix for parameters when passed as exchange header properties + String PROPERTY_PREFIX = "CamelBox."; + + // thread profile name for this component + String THREAD_PROFILE_NAME = "CamelBox"; +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxPropertiesHelper.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxPropertiesHelper.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxPropertiesHelper.java new file mode 100644 index 0000000..ae9b4f6 --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/BoxPropertiesHelper.java @@ -0,0 +1,39 @@ +/** + * 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.box.internal; + +import org.apache.camel.component.box.BoxConfiguration; +import org.apache.camel.util.component.ApiMethodPropertiesHelper; + +/** + * Singleton {@link ApiMethodPropertiesHelper} for Box component. + */ +public final class BoxPropertiesHelper extends ApiMethodPropertiesHelper { + + private static BoxPropertiesHelper helper; + + private BoxPropertiesHelper() { + super(BoxConfiguration.class, BoxConstants.PROPERTY_PREFIX); + } + + public static synchronized BoxPropertiesHelper getHelper() { + if (helper == null) { + helper = new BoxPropertiesHelper(); + } + return helper; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachedBoxClient.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachedBoxClient.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachedBoxClient.java new file mode 100644 index 0000000..2a806ba --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachedBoxClient.java @@ -0,0 +1,72 @@ +/** + * 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.box.internal; + +import com.box.boxjavalibv2.BoxClient; +import org.apache.http.conn.ClientConnectionManager; + +public class CachedBoxClient { + + private final BoxClient boxClient; + + private final String login; + + private final String clientId; + + private final CachingSecureStorage secureStorage; + + private final OAuthHelperListener listener; + + @SuppressWarnings("deprecation") + private final ClientConnectionManager[] clientConnectionManager; + + @SuppressWarnings("deprecation") + public CachedBoxClient(BoxClient boxClient, String login, String clientId, CachingSecureStorage secureStorage, + OAuthHelperListener listener, ClientConnectionManager[] clientConnectionManager) { + this.boxClient = boxClient; + this.login = login; + this.clientId = clientId; + this.secureStorage = secureStorage; + this.listener = listener; + if (clientConnectionManager == null || clientConnectionManager.length != 1) { + throw new IllegalArgumentException("clientConnectionManager must be an array of length 1"); + } + this.clientConnectionManager = clientConnectionManager; + } + + public BoxClient getBoxClient() { + return boxClient; + } + + public CachingSecureStorage getSecureStorage() { + return secureStorage; + } + + public OAuthHelperListener getListener() { + return listener; + } + + @SuppressWarnings("deprecation") + public ClientConnectionManager getClientConnectionManager() { + return clientConnectionManager[0]; + } + + @Override + public String toString() { + return String.format("{login:%s, client_id:%s}", login, clientId); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachingSecureStorage.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachingSecureStorage.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachingSecureStorage.java new file mode 100644 index 0000000..adb606c --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/CachingSecureStorage.java @@ -0,0 +1,50 @@ +/** + * 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.box.internal; + +import com.box.boxjavalibv2.authorization.IAuthSecureStorage; +import com.box.boxjavalibv2.dao.IAuthData; + +/** + * A caching {@link IAuthSecureStorage} that also delegates to another {@link IAuthSecureStorage}. + */ +public class CachingSecureStorage implements IAuthSecureStorage { + + private final IAuthSecureStorage secureStorage; + + private IAuthData auth; + + public CachingSecureStorage(IAuthSecureStorage secureStorage) { + this.secureStorage = secureStorage; + } + + @Override + public void saveAuth(IAuthData newAuth) { + this.auth = newAuth; + if (secureStorage != null) { + secureStorage.saveAuth(newAuth); + } + } + + @Override + public IAuthData getAuth() { + if (auth == null && secureStorage != null) { + auth = secureStorage.getAuth(); + } + return auth; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowListener.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowListener.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowListener.java new file mode 100644 index 0000000..3b0a25d --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowListener.java @@ -0,0 +1,67 @@ +/** + * 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.box.internal; + +import java.util.concurrent.CountDownLatch; + +import com.box.boxjavalibv2.authorization.IAuthEvent; +import com.box.boxjavalibv2.authorization.IAuthFlowListener; +import com.box.boxjavalibv2.authorization.IAuthFlowMessage; +import com.box.boxjavalibv2.events.OAuthEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** +* Implementation of {@link IAuthFlowListener} to get success or failure status of OAuth flow. +*/ +public final class LoginAuthFlowListener implements IAuthFlowListener { + + private static final Logger LOG = LoggerFactory.getLogger(LoginAuthFlowListener.class); + + private final Exception[] exception = new Exception[1]; + private final CountDownLatch latch; + + public LoginAuthFlowListener(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void onAuthFlowMessage(IAuthFlowMessage message) { + // do nothing + } + + @Override + public void onAuthFlowException(Exception e) { + // record exception + exception[0] = e; + LOG.warn(String.format("OAuth exception: %s", e.getMessage()), e); + latch.countDown(); + } + + @Override + public void onAuthFlowEvent(IAuthEvent state, IAuthFlowMessage message) { + // check success + if (state == OAuthEvent.OAUTH_CREATED) { + LOG.debug("OAuth succeeded"); + latch.countDown(); + } + } + + public Exception getException() { + return exception[0]; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowUI.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowUI.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowUI.java new file mode 100644 index 0000000..568d27c --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/LoginAuthFlowUI.java @@ -0,0 +1,190 @@ +/** + * 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.box.internal; + +import java.security.SecureRandom; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.box.boxjavalibv2.BoxClient; +import com.box.boxjavalibv2.authorization.IAuthFlowListener; +import com.box.boxjavalibv2.authorization.IAuthFlowUI; +import com.box.boxjavalibv2.authorization.OAuthDataMessage; +import com.box.boxjavalibv2.authorization.OAuthWebViewData; +import com.box.boxjavalibv2.dao.BoxOAuthToken; +import com.box.boxjavalibv2.events.OAuthEvent; +import com.box.boxjavalibv2.resourcemanagers.IBoxOAuthManager; +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.ProxyConfig; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebClientOptions; +import com.gargoylesoftware.htmlunit.html.HtmlButton; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import org.apache.camel.component.box.BoxConfiguration; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.conn.params.ConnRoutePNames; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** +* HtmlUnit based OAuth2 implementation of {@link IAuthFlowUI} +*/ +public final class LoginAuthFlowUI implements IAuthFlowUI { + + private static final Logger LOG = LoggerFactory.getLogger(LoginAuthFlowUI.class); + private static final Pattern QUERY_PARAM_PATTERN = Pattern.compile("&?([^=]+)=([^&]+)"); + + private final BoxConfiguration configuration; + private final BoxClient boxClient; + + private IAuthFlowListener listener; + + public LoginAuthFlowUI(BoxConfiguration configuration, BoxClient boxClient) { + this.configuration = configuration; + this.boxClient = boxClient; + } + + @SuppressWarnings("deprecation") + @Override + public void authenticate(IAuthFlowListener listener) { + + // TODO run this on an Executor to make it async + + // create HtmlUnit client + final WebClient webClient = new WebClient(BrowserVersion.FIREFOX_24); + final WebClientOptions options = webClient.getOptions(); + options.setRedirectEnabled(true); + options.setJavaScriptEnabled(false); + options.setThrowExceptionOnFailingStatusCode(true); + options.setThrowExceptionOnScriptError(true); + options.setPrintContentOnFailingStatusCode(LOG.isDebugEnabled()); + + // add HTTP proxy if set + final Map httpParams = configuration.getHttpParams(); + if (httpParams != null && httpParams.get(ConnRoutePNames.DEFAULT_PROXY) != null) { + final HttpHost proxyHost = (HttpHost) httpParams.get(ConnRoutePNames.DEFAULT_PROXY); + final Boolean socksProxy = (Boolean) httpParams.get("http.route.socks-proxy"); + final ProxyConfig proxyConfig = new ProxyConfig(proxyHost.getHostName(), proxyHost.getPort(), + socksProxy != null ? socksProxy : false); + options.setProxyConfig(proxyConfig); + } + + // authorize application on user's behalf + try { + final String csrfId = String.valueOf(new SecureRandom().nextLong()); + + OAuthWebViewData viewData = new OAuthWebViewData(boxClient.getOAuthDataController()); + viewData.setOptionalState(String.valueOf(csrfId)); + final HtmlPage authPage = webClient.getPage(viewData.buildUrl().toString()); + + // submit login credentials + final HtmlForm loginForm = authPage.getFormByName("login_form"); + final HtmlTextInput login = loginForm.getInputByName("login"); + login.setText(configuration.getUserName()); + final HtmlPasswordInput password = loginForm.getInputByName("password"); + password.setText(configuration.getUserPassword()); + final HtmlSubmitInput submitInput = loginForm.getInputByName("login_submit"); + + // submit consent + final HtmlPage consentPage = submitInput.click(); + final HtmlForm consentForm = consentPage.getFormByName("consent_form"); + final HtmlButton consentAccept = consentForm.getButtonByName("consent_accept"); + + // disable redirect to avoid loading redirect URL + webClient.getOptions().setRedirectEnabled(false); + + // validate CSRF and get authorization code + String redirectQuery; + try { + final Page redirectPage = consentAccept.click(); + redirectQuery = redirectPage.getUrl().getQuery(); + } catch (FailingHttpStatusCodeException e) { + // escalate non redirect errors + if (e.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { + throw e; + } + final String location = e.getResponse().getResponseHeaderValue("Location"); + redirectQuery = location.substring(location.indexOf('?') + 1); + } + final Map params = new HashMap(); + final Matcher matcher = QUERY_PARAM_PATTERN.matcher(redirectQuery); + while (matcher.find()) { + params.put(matcher.group(1), matcher.group(2)); + } + final String state = params.get("state"); + if (!csrfId.equals(state)) { + final SecurityException e = new SecurityException("Invalid CSRF code!"); + listener.onAuthFlowException(e); + this.listener.onAuthFlowException(e); + } else { + + // get authorization code + final String authorizationCode = params.get("code"); + + // get OAuth token + final IBoxOAuthManager oAuthManager = boxClient.getOAuthManager(); + final BoxOAuthToken oAuthToken = oAuthManager.createOAuth(authorizationCode, + configuration.getClientId(), configuration.getClientSecret(), null); + + // send initial token to BoxClient and this.listener + final OAuthDataMessage authDataMessage = new OAuthDataMessage(oAuthToken, + boxClient.getJSONParser(), boxClient.getResourceHub()); + listener.onAuthFlowEvent(OAuthEvent.OAUTH_CREATED, authDataMessage); + this.listener.onAuthFlowEvent(OAuthEvent.OAUTH_CREATED, authDataMessage); + } + + } catch (Exception e) { + // forward login exceptions to listener + listener.onAuthFlowException(e); + this.listener.onAuthFlowException(e); + } + } + + @Override + public void addAuthFlowListener(IAuthFlowListener listener) { + this.listener = listener; + } + + @Override + public void initializeAuthFlow(Object applicationContext, String clientId, String clientSecret) { + // unknown usage + throw new UnsupportedOperationException("initializeAuthFlow"); + } + + @Override + public void initializeAuthFlow(Object applicationContext, String clientId, String clientSecret, + String redirectUrl) { + // unknown usage + throw new UnsupportedOperationException("initializeAuthFlow"); + } + + @Override + public void initializeAuthFlow(Object applicationContext, String clientId, String clientSecret, + String redirectUrl, BoxClient boxClient) { + // unknown usage + throw new UnsupportedOperationException("initializeAuthFlow"); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/java/org/apache/camel/component/box/internal/OAuthHelperListener.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/java/org/apache/camel/component/box/internal/OAuthHelperListener.java b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/OAuthHelperListener.java new file mode 100644 index 0000000..684d1ba --- /dev/null +++ b/components/camel-box/src/main/java/org/apache/camel/component/box/internal/OAuthHelperListener.java @@ -0,0 +1,53 @@ +/** + * 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.box.internal; + +import com.box.boxjavalibv2.authorization.IAuthSecureStorage; +import com.box.boxjavalibv2.authorization.OAuthRefreshListener; +import com.box.boxjavalibv2.dao.IAuthData; + +/** +* Wrapper implementation of {@link OAuthRefreshListener} that + * delegates to an {@link IAuthSecureStorage} and another {@link OAuthRefreshListener}. +*/ +class OAuthHelperListener implements OAuthRefreshListener { + private final IAuthSecureStorage authSecureStorage; + private final OAuthRefreshListener configListener; + + private String refreshToken; + + public OAuthHelperListener(IAuthSecureStorage authSecureStorage, OAuthRefreshListener configListener) { + this.authSecureStorage = authSecureStorage; + this.configListener = configListener; + + if (authSecureStorage != null && authSecureStorage.getAuth() != null) { + this.refreshToken = authSecureStorage.getAuth().getRefreshToken(); + } + } + + @Override + public void onRefresh(IAuthData newAuthData) { + // look for refresh token update or revocation + if (authSecureStorage != null + && (newAuthData == null || !newAuthData.getRefreshToken().equals(refreshToken))) { + authSecureStorage.saveAuth(newAuthData); + } + if (configListener != null) { + configListener.onRefresh(newAuthData); + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/main/resources/META-INF/services/org/apache/camel/component/box ---------------------------------------------------------------------- diff --git a/components/camel-box/src/main/resources/META-INF/services/org/apache/camel/component/box b/components/camel-box/src/main/resources/META-INF/services/org/apache/camel/component/box new file mode 100644 index 0000000..43c4063 --- /dev/null +++ b/components/camel-box/src/main/resources/META-INF/services/org/apache/camel/component/box @@ -0,0 +1,18 @@ +# +# 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. +# + +class=org.apache.camel.component.box.BoxComponent http://git-wip-us.apache.org/repos/asf/camel/blob/a4c8f76c/components/camel-box/src/test/java/org/apache/camel/component/box/AbstractBoxTestSupport.java ---------------------------------------------------------------------- diff --git a/components/camel-box/src/test/java/org/apache/camel/component/box/AbstractBoxTestSupport.java b/components/camel-box/src/test/java/org/apache/camel/component/box/AbstractBoxTestSupport.java new file mode 100644 index 0000000..7d2c33c --- /dev/null +++ b/components/camel-box/src/test/java/org/apache/camel/component/box/AbstractBoxTestSupport.java @@ -0,0 +1,165 @@ +/** + * 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.box; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import com.box.boxjavalibv2.authorization.IAuthSecureStorage; +import com.box.boxjavalibv2.authorization.OAuthRefreshListener; +import com.box.boxjavalibv2.dao.BoxOAuthToken; +import com.box.boxjavalibv2.dao.IAuthData; +import com.box.boxjavalibv2.requests.requestobjects.BoxPagingRequestObject; +import com.box.restclientv2.requestsbase.BoxDefaultRequestObject; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelExecutionException; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.apache.camel.util.IntrospectionSupport; +import org.apache.camel.util.ObjectHelper; +import org.junit.AfterClass; + +public abstract class AbstractBoxTestSupport extends CamelTestSupport { + + protected static final String CAMEL_TEST_TAG = "camel_was_here"; + private static final String LINE_SEPARATOR = System.getProperty("line.separator"); + private static final String TEST_OPTIONS_PROPERTIES = "/test-options.properties"; + private static final String REFRESH_TOKEN_PROPERTY = "refreshToken"; + protected static final BoxDefaultRequestObject BOX_DEFAULT_REQUEST_OBJECT = new BoxDefaultRequestObject(); + protected static final BoxPagingRequestObject BOX_PAGING_REQUEST_OBJECT = BoxPagingRequestObject.pagingRequestObject(100, 0); + protected static String testUserId; + + private static String refreshToken; + private static String propertyText; + + protected static String testFolderId; + protected static String testFileId; + + @Override + protected CamelContext createCamelContext() throws Exception { + + final InputStream in = getClass().getResourceAsStream(TEST_OPTIONS_PROPERTIES); + if (in == null) { + throw new IOException(TEST_OPTIONS_PROPERTIES + " could not be found"); + } + + final StringBuilder builder = new StringBuilder(); + final BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + String line; + while((line = reader.readLine()) != null) { + builder.append(line).append(LINE_SEPARATOR); + } + propertyText = builder.toString(); + + final Properties properties = new Properties(); + try { + properties.load(new StringReader(propertyText)); + } catch (IOException e) { + throw new IOException(String.format("%s could not be loaded: %s", TEST_OPTIONS_PROPERTIES, e.getMessage()), + e); + } + + // cache test properties + refreshToken = properties.getProperty(REFRESH_TOKEN_PROPERTY); + testFolderId = properties.getProperty("testFolderId"); + testFileId = properties.getProperty("testFileId"); + testUserId = properties.getProperty("testUserId"); + + Map options = new HashMap(); + for (Map.Entry entry : properties.entrySet()) { + options.put(entry.getKey().toString(), entry.getValue()); + } + + final BoxConfiguration configuration = new BoxConfiguration(); + IntrospectionSupport.setProperties(configuration, options); + configuration.setAuthSecureStorage(new IAuthSecureStorage() { + + @Override + public void saveAuth(IAuthData auth) { + if (auth == null) { + // revoked + refreshToken = ""; + } else { + // remember the refresh token to write back to test-options.properties + refreshToken = auth.getRefreshToken(); + } + } + + @Override + public IAuthData getAuth() { + if (ObjectHelper.isEmpty(refreshToken)) { + return null; + } else { + Map values = new HashMap(); + values.put(BoxOAuthToken.FIELD_REFRESH_TOKEN, refreshToken); + return new BoxOAuthToken(values); + } + } + }); + configuration.setRefreshListener(new OAuthRefreshListener() { + @Override + public void onRefresh(IAuthData newAuthData) { + log.debug("Refreshed OAuth data: " + ((newAuthData != null) ? newAuthData.getAccessToken() : null)); + } + }); + + // add BoxComponent to Camel context + final CamelContext context = super.createCamelContext(); + final BoxComponent component = new BoxComponent(context); + + component.setConfiguration(configuration); + context.addComponent("box", component); + + return context; + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + CamelTestSupport.tearDownAfterClass(); + + // write the refresh token back to target/test-classes/test-options.properties + final URL resource = AbstractBoxTestSupport.class.getResource(TEST_OPTIONS_PROPERTIES); + final FileOutputStream out = new FileOutputStream(new File(resource.getPath())); + propertyText = propertyText.replaceAll(REFRESH_TOKEN_PROPERTY + "=\\S*", + REFRESH_TOKEN_PROPERTY + "=" + refreshToken); + out.write(propertyText.getBytes("UTF-8")); + out.close(); + } + + @Override + public boolean isCreateCamelContextPerClass() { + // only create the context once for this class + return true; + } + + protected T requestBodyAndHeaders(String endpointUri, Object body, Map headers) + throws CamelExecutionException { + return (T) template().requestBodyAndHeaders(endpointUri, body, headers); + } + + protected T requestBody(String endpoint, Object body) throws CamelExecutionException { + return (T) template().requestBody(endpoint, body); + } +}