You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2018/04/19 07:27:10 UTC
[camel] branch master updated: [CAMEL-11257] First Phase of AS2
Camel Component (#2300)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push:
new 486bae5 [CAMEL-11257] First Phase of AS2 Camel Component (#2300)
486bae5 is described below
commit 486bae5cda200f4c8b0e74f18ed25af3b4bbb988
Author: William Collins <wc...@redhat.com>
AuthorDate: Thu Apr 19 03:27:06 2018 -0400
[CAMEL-11257] First Phase of AS2 Camel Component (#2300)
* [CAMEL-11257] Initial commit AS2 Component
* [CAMEL-11257] Refactored connection logic into
AS2 client and server connection objects
* Updated Camel version for AS2 Component
* [CAMEL-11257] Updated Integration Tests
* [ENTESB-6202] Refactored client and server connection
code and added api unit test.
* [CAMEL-11257] Added unit test
* [CAMEL-11257] Updated api layer to receive parameters via HTTP context.
* [CAMEL-11257] Updated AS2MessageTest
* [CAMEL-11257] Updated unit tests
* [CAMEL-11257] Updated Client Send Test, refactored Application EDI Entity parsing and added partial implementation of Multipart Signed Entity parsing.
* [CAMEL-11257] Partial implementation of signature validation
* [CAMEL-11257] Fixes and testing of signature validation
* [CAMEL-11257] Fixes for signature validation
* [CAMEL-11257] Refactored Entity parsing logic
* [CAMEL-11257] Updated tests and configuration.
* [CAMEL-11257] Updated component tests
* [CAMEL-11257] Refactored ConnectionHelper and updated Server Manager Integration test.
* [CAMEL-11257] Updated Server Manager integration tests.
* [CAMEL-11257] Added MDN request protocol support
* [CAMEL-11257] Initial work on MDN entities
* [CAMEL-11257] Continuing work on MDN entities
* [CAMEL-11257] Continuing work on MDN entities
* [CAMEL-11257] Continuing work on MDN entities
* [CAMEL-11257] Continuing work on MDN entities
* [CAMEL-11257] Continuing work on MDN entities
* [CAMEL-11257] Added unit test.
* [CAMEL-11257] Added and updated unit tests.
* [CAMEL-11257] Adding MDN response handling to AS2 Server connections
* [CAMEL-11257] Further work on MDN response handling for AS2 Server connections
* [CAMEL-11257] Further work on MDN response handling for AS2 Server connections and refactoring
* [CAMEL-11257] Further work on MDN response handling for AS2 Server connections
* [CAMEL-11257] Added MDN entity parsing logic
* [CAMEL-11257] Updated Disposition Notification Content Utils Tests
* [CAMEL-11257] Refactored MDN parsing logic
* [CAMEL-11257] Added Entity Parser Test
* [CAMEL-11257] Updating component integration tests
* [CAMEL-11257] Updating component integration tests
* [CAMEL-11257] Updating component integration tests
* [CAMEL-11257] Fixed unit test.
* [CAMEL-11257] Further refactoring
* [CAMEL-11257] Further refactoring
* [CAMEL-11257] Further refactoring
* [CAMEL-11257] Moved HTTP connection class to io package.
* [CAMEL-11257] Adding initial code for signed receipts
* [CAMEL-11257] Added code for signed receipts
* [CAMEL-11257] Clean up after rebase with upstream.
* [CAMEL-11257] Checkstyle clean up
* [CAMEL-11257] Updated poms
* [CAMEL-11257] Added comments as placeholders for future code.
---
components/camel-as2/camel-as2-api/keystore.pfx | Bin 0 -> 4536 bytes
components/camel-as2/camel-as2-api/pom.xml | 132 ++++
.../apache/camel/component/as2/api/AS2Charset.java | 30 +
.../component/as2/api/AS2ClientConnection.java | 103 +++
.../camel/component/as2/api/AS2ClientManager.java | 265 +++++++
.../camel/component/as2/api/AS2Constants.java | 90 +++
.../apache/camel/component/as2/api/AS2Header.java | 116 +++
.../camel/component/as2/api/AS2MediaType.java | 45 ++
.../component/as2/api/AS2MessageStructure.java | 44 ++
.../camel/component/as2/api/AS2MicAlgorithm.java | 69 ++
.../camel/component/as2/api/AS2MimeType.java | 57 ++
.../camel/component/as2/api/AS2ReportType.java | 26 +
.../component/as2/api/AS2ServerConnection.java | 217 ++++++
.../camel/component/as2/api/AS2ServerManager.java | 86 +++
.../component/as2/api/AS2SignedDataGenerator.java | 191 +++++
.../component/as2/api/AS2TransferEncoding.java | 23 +
.../component/as2/api/CanonicalOutputStream.java | 80 ++
.../component/as2/api/InvalidAS2NameException.java | 73 ++
.../apache/camel/component/as2/api/MDNField.java | 66 ++
.../org/apache/camel/component/as2/api/Util.java | 183 +++++
.../as2/api/entity/AS2DispositionModifier.java | 91 +++
.../as2/api/entity/AS2DispositionType.java | 48 ++
.../AS2MessageDispositionNotificationEntity.java | 262 +++++++
.../api/entity/ApplicationEDIConsentEntity.java | 29 +
.../as2/api/entity/ApplicationEDIEntity.java | 64 ++
.../as2/api/entity/ApplicationEDIFACTEntity.java | 29 +
.../as2/api/entity/ApplicationEDIX12Entity.java | 29 +
.../entity/ApplicationPkcs7SignatureEntity.java | 125 +++
.../component/as2/api/entity/DispositionMode.java | 60 ++
...spositionNotificationMultipartReportEntity.java | 101 +++
.../api/entity/DispositionNotificationOptions.java | 39 +
.../DispositionNotificationOptionsParser.java | 69 ++
.../component/as2/api/entity/EntityParser.java | 854 +++++++++++++++++++++
.../camel/component/as2/api/entity/Importance.java | 53 ++
.../camel/component/as2/api/entity/MimeEntity.java | 277 +++++++
.../as2/api/entity/MultipartMimeEntity.java | 121 +++
.../as2/api/entity/MultipartReportEntity.java | 39 +
.../as2/api/entity/MultipartSignedEntity.java | 109 +++
.../component/as2/api/entity/TextPlainEntity.java | 66 ++
.../as2/api/io/AS2BHttpClientConnection.java | 64 ++
.../as2/api/io/AS2BHttpServerConnection.java | 66 ++
.../as2/api/io/AS2SessionInputBuffer.java | 364 +++++++++
.../component/as2/api/protocol/RequestAS2.java | 84 ++
.../component/as2/api/protocol/RequestMDN.java | 62 ++
.../component/as2/api/protocol/ResponseMDN.java | 181 +++++
.../component/as2/api/util/AS2HeaderUtils.java | 165 ++++
.../component/as2/api/util/ContentTypeUtils.java | 50 ++
.../util/DispositionNotificationContentUtils.java | 300 ++++++++
.../camel/component/as2/api/util/EntityUtils.java | 247 ++++++
.../component/as2/api/util/HttpMessageUtils.java | 108 +++
.../camel/component/as2/api/util/MicUtils.java | 151 ++++
.../camel/component/as2/api/util/SigningUtils.java | 92 +++
.../camel/component/as2/api/AS2MessageTest.java | 367 +++++++++
.../org/apache/camel/component/as2/api/Utils.java | 83 ++
.../DispositionNotificationOptionsParserTest.java | 53 ++
.../component/as2/api/entity/EntityParserTest.java | 239 ++++++
.../component/as2/api/util/AS2HeaderUtilsTest.java | 55 ++
.../DispositionNotificationContentUtilsTest.java | 106 +++
.../camel/component/as2/api/util/MicUtilsTest.java | 111 +++
.../src/test/resources/log4j2.properties | 23 +
components/camel-as2/camel-as2-component/pom.xml | 268 +++++++
.../src/main/java/META-INF/MANIFEST.MF | 3 +
.../apache/camel/component/as2/AS2Component.java | 59 ++
.../camel/component/as2/AS2Configuration.java | 432 +++++++++++
.../apache/camel/component/as2/AS2Consumer.java | 116 +++
.../apache/camel/component/as2/AS2Endpoint.java | 162 ++++
.../apache/camel/component/as2/AS2Producer.java | 31 +
.../as2/internal/AS2ConnectionHelper.java | 73 ++
.../camel/component/as2/internal/AS2Constants.java | 29 +
.../as2/internal/AS2PropertiesHelper.java | 39 +
.../services/org/apache/camel/component/as2 | 1 +
.../as2/AS2ClientManagerIntegrationTest.java | 210 +++++
.../as2/AS2ServerManagerIntegrationTest.java | 310 ++++++++
.../component/as2/AbstractAS2TestSupport.java | 82 ++
.../java/org/apache/camel/component/as2/Utils.java | 83 ++
.../src/test/resources/log4j2.properties | 23 +
.../src/test/resources/test-options.properties | 36 +
components/camel-as2/pom.xml | 48 ++
components/pom.xml | 1 +
platforms/spring-boot/components-starter/pom.xml | 1 +
80 files changed, 9339 insertions(+)
diff --git a/components/camel-as2/camel-as2-api/keystore.pfx b/components/camel-as2/camel-as2-api/keystore.pfx
new file mode 100644
index 0000000..d3804d6
Binary files /dev/null and b/components/camel-as2/camel-as2-api/keystore.pfx differ
diff --git a/components/camel-as2/camel-as2-api/pom.xml b/components/camel-as2/camel-as2-api/pom.xml
new file mode 100644
index 0000000..de1c893
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/pom.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-as2-parent</artifactId>
+ <version>2.22.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-as2-api</artifactId>
+ <name>Camel :: AS2 :: API</name>
+ <description>Camel AS2 API</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-debug-jdk15on</artifactId>
+ <version>1.58</version>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcpkix-jdk15on</artifactId>
+ <version>1.58</version>
+ </dependency>
+
+ <!-- logging -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <defaultGoal>install</defaultGoal>
+
+ <plugins>
+
+ <!-- to generate API Javadoc -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-javadoc</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <attach>true</attach>
+ <source>1.8</source>
+ <quiet>true</quiet>
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <javadocVersion>1.7</javadocVersion>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <!-- Disable Java 8 doclint checks to avoid Javadoc plugin failures -->
+ <profiles>
+ <profile>
+ <id>doclint-java8-disable</id>
+ <activation>
+ <jdk>[1.8,</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <additionalparam>-Xdoclint:none</additionalparam>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+</project>
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Charset.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Charset.java
new file mode 100644
index 0000000..2b43afd
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Charset.java
@@ -0,0 +1,30 @@
+/**
+ * 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.as2.api;
+
+public interface AS2Charset {
+
+ /**
+ * Name of charset parameter in Content-Type header values.
+ */
+ public static final String PARAM = "charset";
+
+ /**
+ * Character Set Name for US ASCII
+ */
+ public static final String US_ASCII = "US-ASCII";
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
new file mode 100644
index 0000000..672f25d
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
@@ -0,0 +1,103 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+
+import org.apache.camel.component.as2.api.io.AS2BHttpClientConnection;
+import org.apache.camel.component.as2.api.protocol.RequestAS2;
+import org.apache.camel.component.as2.api.protocol.RequestMDN;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.impl.DefaultBHttpClientConnection;
+import org.apache.http.protocol.HttpCoreContext;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpProcessorBuilder;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.http.protocol.RequestConnControl;
+import org.apache.http.protocol.RequestContent;
+import org.apache.http.protocol.RequestDate;
+import org.apache.http.protocol.RequestExpectContinue;
+import org.apache.http.protocol.RequestTargetHost;
+import org.apache.http.protocol.RequestUserAgent;
+import org.apache.http.util.Args;
+
+public class AS2ClientConnection {
+
+ private HttpHost targetHost;
+ private HttpProcessor httpProcessor;
+ private DefaultBHttpClientConnection httpConnection;
+ private String as2Version;
+ private String userAgent;
+ private String clientFqdn;
+
+ public AS2ClientConnection(String as2Version, String userAgent, String clientFqdn, String targetHostName, Integer targetPortNumber) throws UnknownHostException, IOException {
+
+ this.as2Version = Args.notNull(as2Version, "as2Version");
+ this.userAgent = Args.notNull(userAgent, "userAgent");
+ this.clientFqdn = Args.notNull(clientFqdn, "clientFqdn");
+ this.targetHost = new HttpHost(Args.notNull(targetHostName, "targetHostName"), Args.notNull(targetPortNumber, "targetPortNumber"));
+
+ // Build Processor
+ httpProcessor = HttpProcessorBuilder.create()
+ .add(new RequestAS2(as2Version, clientFqdn))
+ .add(new RequestMDN())
+ .add(new RequestTargetHost())
+ .add(new RequestUserAgent(this.userAgent))
+ .add(new RequestDate())
+ .add(new RequestContent(true))
+ .add(new RequestConnControl())
+ .add(new RequestExpectContinue(true)).build();
+
+ // Create Socket
+ Socket socket = new Socket(targetHost.getHostName(), targetHost.getPort());
+
+ // Create Connection
+ httpConnection = new AS2BHttpClientConnection(8 * 1024);
+ httpConnection.bind(socket);
+ }
+
+ public String getAs2Version() {
+ return as2Version;
+ }
+
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ public String getClientFqdn() {
+ return clientFqdn;
+ }
+
+ public HttpResponse send(HttpRequest request, HttpCoreContext httpContext) throws HttpException, IOException {
+
+ httpContext.setTargetHost(targetHost);
+
+ // Execute Request
+ HttpRequestExecutor httpexecutor = new HttpRequestExecutor();
+ httpexecutor.preProcess(request, httpProcessor, httpContext);
+ HttpResponse response = httpexecutor.execute(request, httpConnection, httpContext);
+ httpexecutor.postProcess(response, httpProcessor, httpContext);
+
+ return response;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java
new file mode 100644
index 0000000..8d62876
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java
@@ -0,0 +1,265 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+
+import org.apache.camel.component.as2.api.entity.ApplicationEDIEntity;
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.camel.component.as2.api.util.SigningUtils;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.protocol.HttpCoreContext;
+import org.apache.http.util.Args;
+
+/**
+ * AS2 Client Manager
+ *
+ * <p>
+ * Sends EDI Messages over HTTP
+ *
+ */
+public class AS2ClientManager {
+
+ //
+ // AS2 HTTP Context Attribute Keys
+ //
+
+ /**
+ * Prefix for all AS2 HTTP Context Attributes used by the Http Client
+ * Manager.
+ */
+ public static final String CAMEL_AS2_CLIENT_PREFIX = "camel-as2.client";
+
+ /**
+ * The HTTP Context Attribute indicating the AS2 message structure to be sent.
+ */
+ public static final String AS2_MESSAGE_STRUCTURE = CAMEL_AS2_CLIENT_PREFIX + "as2-message-structure";
+
+ /**
+ * The HTTP Context Attribute indicating the EDI message content type to be sent.
+ */
+ public static final String EDI_MESSAGE_CONTENT_TYPE = CAMEL_AS2_CLIENT_PREFIX + "edi-message-content-type";
+
+ /**
+ * The HTTP Context Attribute indicating the EDI message transfer encoding to be sent.
+ */
+ public static final String EDI_MESSAGE_TRANSFER_ENCODING = CAMEL_AS2_CLIENT_PREFIX + "edi-message-transfer-encoding";
+
+ /**
+ * The HTTP Context Attribute containing the HTTP request message
+ * transporting the EDI message
+ */
+ public static final String HTTP_REQUEST = HttpCoreContext.HTTP_REQUEST;
+
+ /**
+ * The HTTP Context Attribute containing the HTTP response message
+ * transporting the EDI message
+ */
+ public static final String HTTP_RESPONSE = HttpCoreContext.HTTP_RESPONSE;
+
+ /**
+ * The HTTP Context Attribute containing the AS2 Connection used to send
+ * request message.
+ */
+ public static final String AS2_CONNECTION = CAMEL_AS2_CLIENT_PREFIX + "as2-connection";
+
+ /**
+ * The HTTP Context Attribute containing the request URI identifying the
+ * process on the receiving system responsible for unpacking and handling of
+ * message data and generating a reply for the sending system that contains
+ * a Message Disposition Acknowledgement (MDN)
+ */
+ public static final String REQUEST_URI = CAMEL_AS2_CLIENT_PREFIX + "request-uri";
+
+ /**
+ * The HTTP Context Attribute containing the subject header sent in an AS2
+ * message.
+ */
+ public static final String SUBJECT = CAMEL_AS2_CLIENT_PREFIX + "subject";
+
+ /**
+ * The HTTP Context Attribute containing the internet e-mail address of
+ * sending system
+ */
+ public static final String FROM = CAMEL_AS2_CLIENT_PREFIX + "from";
+
+ /**
+ * The HTTP Context Attribute containing the AS2 System Identifier of the
+ * sending system
+ */
+ public static final String AS2_FROM = CAMEL_AS2_CLIENT_PREFIX + "as2-from";
+
+ /**
+ * The HTTP Context Attribute containing the AS2 System Identifier of the
+ * receiving system
+ */
+ public static final String AS2_TO = CAMEL_AS2_CLIENT_PREFIX + "as2-to";
+
+ /**
+ * The HTTP Context Attribute containing the algorithm name used to sign EDI
+ * message
+ */
+ public static final String SIGNING_ALGORITHM_NAME = CAMEL_AS2_CLIENT_PREFIX + "signing-algorithm-name";
+
+ /**
+ * The HTTP Context Attribute containing the certificate chain used to sign
+ * EDI message
+ */
+ public static final String SIGNING_CERTIFICATE_CHAIN = CAMEL_AS2_CLIENT_PREFIX + "signing-certificate-chain";
+
+ /**
+ * The HTTP Context Attribute containing the private key used to sign EDI
+ * message
+ */
+ public static final String SIGNING_PRIVATE_KEY = CAMEL_AS2_CLIENT_PREFIX + "signing-private-key";
+
+ /**
+ * The HTTP Context Attribute containing the internet e-mail address of
+ * sending system requesting a message disposition notification.
+ */
+ public static final String DISPOSITION_NOTIFICATION_TO = CAMEL_AS2_CLIENT_PREFIX + "disposition-notification-to";
+
+ /**
+ * The HTTP Context Attribute containing the list of names of the requested MIC algorithms to be used
+ * by the receiving system to construct a message disposition notification.
+ */
+ public static final String SIGNED_RECEIPT_MIC_ALGORITHMS = CAMEL_AS2_CLIENT_PREFIX + "signed-receipt-mic-algorithms";
+
+ //
+
+ private AS2ClientConnection as2ClientConnection;
+
+ public AS2ClientManager(AS2ClientConnection as2ClientConnection) {
+ this.as2ClientConnection = as2ClientConnection;
+ }
+
+ /**
+ * Send <code>ediMessage</code> to trading partner.
+ *
+ * @param ediMessage
+ * - EDI message to transport
+ * @param httpContext
+ * - the subject sent in the interchange request.
+ * @throws HttpException
+ */
+ public HttpCoreContext send(String ediMessage,
+ String requestUri,
+ String subject,
+ String from,
+ String as2From,
+ String as2To,
+ AS2MessageStructure as2MessageStructure,
+ ContentType ediMessageContentType,
+ String ediMessageTransferEncoding,
+ Certificate[] signingCertificateChain,
+ PrivateKey signingPrivateKey,
+ String dispositionNotificationTo,
+ String[] signedReceiptMicAlgorithms)
+ throws HttpException {
+
+ Args.notNull(ediMessage, "EDI Message");
+ Args.notNull(as2MessageStructure, "AS2 Message Structure");
+ Args.notNull(requestUri, "Request URI");
+ Args.notNull(ediMessageContentType, "EDI Message Content Type");
+
+ // Add Context attributes
+ HttpCoreContext httpContext = HttpCoreContext.create();
+ httpContext.setAttribute(AS2ClientManager.REQUEST_URI, requestUri);
+ httpContext.setAttribute(AS2ClientManager.SUBJECT, subject);
+ httpContext.setAttribute(AS2ClientManager.FROM, from);
+ httpContext.setAttribute(AS2ClientManager.AS2_FROM, as2From);
+ httpContext.setAttribute(AS2ClientManager.AS2_TO, as2To);
+ httpContext.setAttribute(AS2ClientManager.AS2_MESSAGE_STRUCTURE, as2MessageStructure);
+ httpContext.setAttribute(AS2ClientManager.EDI_MESSAGE_CONTENT_TYPE, ediMessageContentType);
+ httpContext.setAttribute(AS2ClientManager.EDI_MESSAGE_TRANSFER_ENCODING, ediMessageTransferEncoding);
+ httpContext.setAttribute(AS2ClientManager.SIGNING_CERTIFICATE_CHAIN, signingCertificateChain);
+ httpContext.setAttribute(AS2ClientManager.SIGNING_PRIVATE_KEY, signingPrivateKey);
+ httpContext.setAttribute(AS2ClientManager.DISPOSITION_NOTIFICATION_TO, dispositionNotificationTo);
+ httpContext.setAttribute(AS2ClientManager.SIGNED_RECEIPT_MIC_ALGORITHMS, signedReceiptMicAlgorithms);
+
+ BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", requestUri);
+ httpContext.setAttribute(HTTP_REQUEST, request);
+
+ // Create Message Body
+ ApplicationEDIEntity applicationEDIEntity;
+ try {
+ applicationEDIEntity = EntityUtils.createEDIEntity(ediMessage, ediMessageContentType, ediMessageTransferEncoding, false);
+ } catch (Exception e) {
+ throw new HttpException("Failed to create EDI message entity", e);
+ }
+ switch (as2MessageStructure) {
+ case PLAIN:
+ applicationEDIEntity.setMainBody(true);
+ EntityUtils.setMessageEntity(request, applicationEDIEntity);
+ break;
+ case SIGNED:
+ AS2SignedDataGenerator gen = createSigningGenerator(httpContext);
+ // Create Multipart Signed Entity
+ try {
+ MultipartSignedEntity multipartSignedEntity = new MultipartSignedEntity(applicationEDIEntity, gen,
+ AS2Charset.US_ASCII, AS2TransferEncoding.BASE64, true, null);
+ EntityUtils.setMessageEntity(request, multipartSignedEntity);
+ } catch (Exception e) {
+ throw new HttpException("Failed to sign message", e);
+ }
+ break;
+ case ENCRYPTED:
+ // TODO : Add code here to add application/pkcs7-mime entity when encryption facility available.
+ break;
+ case ENCRYPTED_SIGNED:
+ // TODO : Add code here to add application/pkcs7-mime entity when encryption facility available.
+ break;
+ default:
+ throw new HttpException("Unknown AS2 Message Structure");
+ }
+
+ HttpResponse response;
+ try {
+ httpContext.setAttribute(AS2_CONNECTION, as2ClientConnection);
+ response = as2ClientConnection.send(request, httpContext);
+ EntityParser.parseAS2MessageEntity(response);
+ } catch (IOException e) {
+ throw new HttpException("Failed to send http request message", e);
+ }
+ httpContext.setAttribute(HTTP_RESPONSE, response);
+ return httpContext;
+ }
+
+ public AS2SignedDataGenerator createSigningGenerator(HttpCoreContext httpContext) throws HttpException {
+
+ Certificate[] certificateChain = httpContext.getAttribute(SIGNING_CERTIFICATE_CHAIN, Certificate[].class);
+ if (certificateChain == null) {
+ throw new HttpException("Signing certificate chain missing");
+ }
+
+ PrivateKey privateKey = httpContext.getAttribute(SIGNING_PRIVATE_KEY, PrivateKey.class);
+ if (privateKey == null) {
+ throw new HttpException("Signing private key missing");
+ }
+
+ return SigningUtils.createSigningGenerator(certificateChain, privateKey);
+
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Constants.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Constants.java
new file mode 100644
index 0000000..5a9faf8
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Constants.java
@@ -0,0 +1,90 @@
+/**
+ * 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.as2.api;
+
+import org.apache.http.protocol.HttpCoreContext;
+
+/**
+ * Constants for AS2 component.
+ */
+public interface AS2Constants {
+
+ /**
+ * The Value of User Agent Header used by AS2 Camel Component.
+ */
+ public static final String HTTP_USER_AGENT = "Camel AS2 Component";
+
+ /**
+ * The Value of Origin Server Header used by AS2 Camel Component.
+ */
+ public static final String HTTP_ORIGIN_SERVER = "Camel AS2 Component";
+
+ /**
+ * Fully Qualified Domain Name used by AS2 Camel Component in Message ID Header.
+ */
+ public static final String HTTP_MESSAGE_ID_FQDN = "camel.apache.org";
+
+ /**
+ * The Value of User Agent Header used by AS2 Camel Component.
+ */
+ public static final String MIME_VERSION = "1.0";
+
+ //
+ // HTTP Context Attribute Names
+ //
+
+ /**
+ * HTTP Context Attribute Name for HTTP Client Connection object stored in context.
+ */
+ public static final String HTTP_CLIENT_CONNECTION = HttpCoreContext.HTTP_CONNECTION;
+
+ /**
+ * HTTP Context Attribute Name for HTTP Client Processor object stored in context.
+ */
+ public static final String HTTP_CLIENT_PROCESSOR = "http.processor";
+
+ /**
+ * HTTP Context Attribute Name for HTTP Client Fully Qualified Domain Name (FQDN) stored in context.
+ */
+ public static final String HTTP_CLIENT_FQDN = "client.fqdn";
+
+ /**
+ * HTTP Context Attribute Name for HTTP Server Connection object stored in context.
+ */
+ public static final String HTTP_SERVER_CONNECTION = "http.server.connection";
+
+ /**
+ * HTTP Context Attribute Name for HTTP Server Processor object stored in context.
+ */
+ public static final String HTTP_SERVER_PROCESSOR = "http.server.processor";
+
+ /**
+ * HTTP Context Attribute Name for HTTP Server Service object stored in context.
+ */
+ public static final String HTTP_SERVER_SERVICE = "http.server.service";
+
+
+ //
+ // AS2 MIME Content Types
+ //
+
+ /**
+ * Application EDIFACT content type
+ */
+ public static final String APPLICATION_EDIFACT_MIME_TYPE = "Application/EDIFACT";
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Header.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Header.java
new file mode 100644
index 0000000..d31d04e
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2Header.java
@@ -0,0 +1,116 @@
+/**
+ * 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.as2.api;
+
+import org.apache.http.protocol.HTTP;
+
+public interface AS2Header {
+
+ /**
+ * Message Header Name for MIME Version
+ */
+ public static final String MIME_VERSION = "MIME-Version";
+ /**
+ * Message Header Name for AS2 From
+ */
+ public static final String AS2_FROM = "AS2-From";
+ /**
+ * Message Header Name for AS2 Version
+ */
+ public static final String AS2_VERSION = "AS2-Version";
+ /**
+ * Message Header Name for Content Type
+ */
+ public static final String CONTENT_TYPE = "Content-Type";
+ /**
+ * Message Header Name for AS2 To
+ */
+ public static final String AS2_TO = "AS2-To";
+ /**
+ * Message Header Name for From
+ */
+ public static final String FROM = "From";
+ /**
+ * Message Header Name for Subject
+ */
+ public static final String SUBJECT = "Subject";
+ /**
+ * Message Header Name for Message ID
+ */
+ public static final String MESSAGE_ID = "Message-Id";
+ /**
+ * Message Header Name for Target Host
+ */
+ public static final String TARGET_HOST = HTTP.TARGET_HOST;
+ /**
+ * Message Header Name for User Agent
+ */
+ public static final String USER_AGENT = HTTP.USER_AGENT;
+ /**
+ * Message Header Name for Server Name
+ */
+ public static final String SERVER = HTTP.SERVER_HEADER;
+ /**
+ * Message Header Name for Date
+ */
+ public static final String DATE = HTTP.DATE_HEADER;
+ /**
+ * Message Header Name for Content Length
+ */
+ public static final String CONTENT_LENGTH = HTTP.CONTENT_LEN;
+ /**
+ * Message Header Name for Connection
+ */
+ public static final String CONNECTION = HTTP.CONN_DIRECTIVE;
+ /**
+ * Message Header Name for Expect
+ */
+ public static final String EXPECT = HTTP.EXPECT_DIRECTIVE;
+ /**
+ * Message Header name for Content Transfer Encoding
+ */
+ public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+ /**
+ * Message Header name for Content Disposition
+ */
+ public static final String CONTENT_DISPOSITION = "Content-Disposition";
+ /**
+ * Message Header name for Content Description
+ */
+ public static final String CONTENT_DESCRIPTION = "Content-Description";
+ /**
+ * Message Header name for Disposition Notification To
+ */
+ public static final String DISPOSITION_NOTIFICATION_TO = "Disposition-Notification-To";
+ /**
+ * Message Header name for Receipt Delivery Option
+ */
+ public static final String RECEIPT_DELIVERY_OPTION = "Receipt-Delivery-Option";
+ /**
+ * Message Header name for Receipt Address
+ */
+ public static final String RECEIPT_ADDRESS = "Receipt-Address";
+ /**
+ * Message Header name for Disposition Notification Options
+ */
+ public static final String DISPOSITION_NOTIFICATION_OPTIONS = "Disposition-Notification-Options";
+ /**
+ * Message Header name for Disposition Notification Options
+ */
+ public static final String REPORT_TYPE = "Report-Type";
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MediaType.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MediaType.java
new file mode 100644
index 0000000..08fbcd9
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MediaType.java
@@ -0,0 +1,45 @@
+/**
+ * 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.as2.api;
+
+public interface AS2MediaType {
+
+ /**
+ * Media Type for Multipart Signed Data
+ */
+ public static final String MULTIPART_SIGNED = "multipart/signed; protocol=\"application/pkcs7-signature\"";
+ /**
+ * Media Type for Application PKCS7 Signature
+ */
+ public static final String APPLICATION_PKCS7_SIGNATURE = "application/pkcs7-signature; name=smime.p7s; smime-type=signed-data";
+ /**
+ * Media Type for Text/Plain Data
+ */
+ public static final String TEXT_PLAIN = "text/plain";
+ /**
+ * Media Type for Application/EDIFACT
+ */
+ public static final String APPLICATION_EDIFACT = "application/edifact";
+ /**
+ * Media Type for Application/EDI-X12
+ */
+ public static final String APPLICATION_EDI_X12 = "application/edi-x12";
+ /**
+ * Media Type for Application/EDI-consent
+ */
+ public static final String APPLICATION_EDI_CONSENT = "application/edi-consent";
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
new file mode 100644
index 0000000..0bb9202
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
@@ -0,0 +1,44 @@
+/**
+ * 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.as2.api;
+
+public enum AS2MessageStructure {
+ PLAIN(false, false, false),
+ SIGNED(false, false, false),
+ ENCRYPTED(false, false, false),
+ ENCRYPTED_SIGNED(false, false, false);
+
+ private final boolean isSigned;
+ private final boolean isEncrypted;
+ private final boolean isCompressed;
+
+ private AS2MessageStructure(boolean isSigned, boolean isEncrypted, boolean isCompressed) {
+ this.isSigned = isSigned;
+ this.isEncrypted = isEncrypted;
+ this.isCompressed = isCompressed;
+ }
+
+ public boolean isSigned() {
+ return isSigned;
+ }
+ public boolean isEncrypted() {
+ return isEncrypted;
+ }
+ public boolean isCompressed() {
+ return isCompressed;
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MicAlgorithm.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MicAlgorithm.java
new file mode 100644
index 0000000..b6c9082
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MicAlgorithm.java
@@ -0,0 +1,69 @@
+/**
+ * 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.as2.api;
+
+interface Constants {
+ static final String SHA_1_AS2_ALGORITHM_NAME = "sha1";
+ static final String SHA_1_JDK_ALGORITHM_NAME = "SHA-1";
+
+ static final String MD5_AS2_ALGORITHM_NAME = "md5";
+ static final String MD5_JDK_ALGORITHM_NAME = "MD5";
+}
+
+public enum AS2MicAlgorithm {
+ SHA_1(Constants.SHA_1_JDK_ALGORITHM_NAME, Constants.SHA_1_AS2_ALGORITHM_NAME),
+ MD5(Constants.MD5_JDK_ALGORITHM_NAME, Constants.MD5_AS2_ALGORITHM_NAME);
+
+ private String jdkAlgorithmName;
+ private String as2AlgorithmName;
+
+ private AS2MicAlgorithm(String jdkAlgorithmName, String as2AlgorithmName) {
+ this.jdkAlgorithmName = jdkAlgorithmName;
+ this.as2AlgorithmName = as2AlgorithmName;
+ }
+
+ public String getJdkAlgorithmName() {
+ return jdkAlgorithmName;
+ }
+
+ public String getAs2AlgorithmName() {
+ return as2AlgorithmName;
+ }
+
+ public static String getJdkAlgorithmName(String as2AlgorithmName) {
+ switch(as2AlgorithmName) {
+ case Constants.SHA_1_AS2_ALGORITHM_NAME:
+ return Constants.SHA_1_JDK_ALGORITHM_NAME;
+ case Constants.MD5_AS2_ALGORITHM_NAME:
+ return Constants.MD5_JDK_ALGORITHM_NAME;
+ default:
+ return null;
+ }
+ }
+
+ public static String getAS2AlgorithmName(String jdkAlgorithmName) {
+ switch(jdkAlgorithmName) {
+ case Constants.MD5_JDK_ALGORITHM_NAME:
+ return Constants.MD5_AS2_ALGORITHM_NAME;
+ case Constants.SHA_1_JDK_ALGORITHM_NAME:
+ return Constants.SHA_1_AS2_ALGORITHM_NAME;
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MimeType.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MimeType.java
new file mode 100644
index 0000000..a1fcd8d
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MimeType.java
@@ -0,0 +1,57 @@
+/**
+ * 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.as2.api;
+
+public interface AS2MimeType {
+ /**
+ * Mime Type for Multipart Signed Data
+ */
+ public static final String MULTIPART_SIGNED = "multipart/signed";
+ /**
+ * Mime Type for Application PKCS7 Signature
+ */
+ public static final String APPLICATION_PKCS7_SIGNATURE = "application/pkcs7-signature";
+ /**
+ * Mime Type for Application PKCS7 Signature
+ */
+ public static final String APPLICATION_PKCS7_MIME = "application/pkcs7-mime";
+ /**
+ * Mime Type for Text/Plain Data
+ */
+ public static final String TEXT_PLAIN = "text/plain";
+ /**
+ * Mime Type for Application/EDIFACT
+ */
+ public static final String APPLICATION_EDIFACT = "application/edifact";
+ /**
+ * Mime Type for Application/EDI-X12
+ */
+ public static final String APPLICATION_EDI_X12 = "application/edi-x12";
+ /**
+ * Mime Type for Application/EDI-consent
+ */
+ public static final String APPLICATION_EDI_CONSENT = "application/edi-consent";
+ /**
+ * Mime Type for Multipart/Report
+ */
+ public static final String MULTIPART_REPORT = "multipart/report";
+ /**
+ * Mime Type for Message/Disposition-Notification
+ */
+ public static final String MESSAGE_DISPOSITION_NOTIFICATION = "message/disposition-notification";
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ReportType.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ReportType.java
new file mode 100644
index 0000000..2f3c969
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ReportType.java
@@ -0,0 +1,26 @@
+/**
+ * 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.as2.api;
+
+public interface AS2ReportType {
+
+ /**
+ * Disposition Notification Report Type
+ */
+ public static final String DISPOSITION_NOTIFICATION = "disposition-notification";
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
new file mode 100644
index 0000000..9e40149
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
@@ -0,0 +1,217 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+
+import org.apache.camel.component.as2.api.io.AS2BHttpServerConnection;
+import org.apache.camel.component.as2.api.protocol.ResponseMDN;
+import org.apache.http.ConnectionClosedException;
+import org.apache.http.HttpException;
+import org.apache.http.HttpInetConnection;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.HttpServerConnection;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpProcessor;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.protocol.HttpService;
+import org.apache.http.protocol.ImmutableHttpProcessor;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+import org.apache.http.protocol.UriHttpRequestHandlerMapper;
+import org.apache.http.util.Args;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AS2ServerConnection {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AS2ServerConnection.class);
+
+ private static final String REQUEST_LISTENER_THREAD_NAME_PREFIX = "AS2Svr-";
+ private static final String REQUEST_HANDLER_THREAD_NAME_PREFIX = "AS2Hdlr-";
+
+ static class RequestListenerThread extends Thread {
+
+ private final ServerSocket serversocket;
+ private final HttpService httpService;
+ private UriHttpRequestHandlerMapper reqistry;
+
+ public RequestListenerThread(String as2Version, String originServer, String serverFqdn, int port, Certificate[] signingCertificateChain, PrivateKey signingPrivateKey) throws IOException {
+ setName(REQUEST_LISTENER_THREAD_NAME_PREFIX + port);
+ serversocket = new ServerSocket(port);
+
+ // Set up HTTP protocol processor for incoming connections
+ final HttpProcessor inhttpproc = new ImmutableHttpProcessor(new HttpResponseInterceptor[] {
+ new ResponseContent(true),
+ new ResponseServer(originServer),
+ new ResponseDate(),
+ new ResponseConnControl(),
+ new ResponseMDN(as2Version, serverFqdn, signingCertificateChain, signingPrivateKey)
+ });
+
+ reqistry = new UriHttpRequestHandlerMapper();
+
+ // Set up the HTTP service
+ httpService = new HttpService(inhttpproc, reqistry);
+ }
+
+ @Override
+ public void run() {
+ LOG.info("Listening on port " + this.serversocket.getLocalPort());
+ while (!Thread.interrupted()) {
+ try {
+ final int bufsize = 8 * 1024;
+ // Set up incoming HTTP connection
+ final Socket insocket = this.serversocket.accept();
+ final AS2BHttpServerConnection inconn = new AS2BHttpServerConnection(bufsize);
+ LOG.info("Incoming connection from " + insocket.getInetAddress());
+ inconn.bind(insocket);
+
+ // Start worker thread
+ final Thread t = new RequestHandlerThread(this.httpService, inconn);
+ t.setDaemon(true);
+ t.start();
+ } catch (final InterruptedIOException ex) {
+ break;
+ } catch (final SocketException e) {
+ // Server socket closed
+ break;
+ } catch (final IOException e) {
+ LOG.error("I/O error initialising connection thread: " + e.getMessage());
+ break;
+ }
+ }
+ }
+
+ void registerHandler(String requestUriPattern, HttpRequestHandler httpRequestHandler) {
+ reqistry.register(requestUriPattern, httpRequestHandler);
+ }
+
+ void unregisterHandler(String requestUri) {
+ reqistry.unregister(requestUri);
+ }
+
+ }
+
+ static class RequestHandlerThread extends Thread {
+ private HttpService httpService;
+ private HttpServerConnection serverConnection;
+
+ public RequestHandlerThread(HttpService httpService, HttpServerConnection serverConnection) {
+ if (serverConnection instanceof HttpInetConnection) {
+ HttpInetConnection inetConnection = (HttpInetConnection) serverConnection;
+ setName(REQUEST_HANDLER_THREAD_NAME_PREFIX + inetConnection.getLocalPort());
+ } else {
+ setName(REQUEST_HANDLER_THREAD_NAME_PREFIX + getId());
+ }
+ this.httpService = httpService;
+ this.serverConnection = serverConnection;
+ }
+
+ @Override
+ public void run() {
+ LOG.info("Processing new AS2 request");
+ final HttpContext context = new BasicHttpContext(null);
+
+ try {
+ while (!Thread.interrupted()) {
+
+ this.httpService.handleRequest(this.serverConnection, context);
+
+ }
+ } catch (final ConnectionClosedException ex) {
+ LOG.info("Client closed connection");
+ } catch (final IOException ex) {
+ LOG.error("I/O error: " + ex.getMessage());
+ } catch (final HttpException ex) {
+ LOG.error("Unrecoverable HTTP protocol violation: " + ex.getMessage());
+ } finally {
+ try {
+ this.serverConnection.shutdown();
+ } catch (final IOException ignore) {
+ }
+ }
+ }
+
+ }
+
+ private RequestListenerThread listenerThread;
+ private String as2Version;
+ private String originServer;
+ private String serverFqdn;
+ private Integer serverPortNumber;
+ private Certificate[] signingCertificateChain;
+ private PrivateKey signingPrivateKey;
+
+ public AS2ServerConnection(String as2Version,
+ String originServer,
+ String serverFqdn,
+ Integer serverPortNumber,
+ Certificate[] signingCertificateChain,
+ PrivateKey signingPrivateKey)
+ throws IOException {
+ this.as2Version = Args.notNull(as2Version, "as2Version");
+ this.originServer = Args.notNull(originServer, "userAgent");
+ this.serverFqdn = Args.notNull(serverFqdn, "serverFqdn");
+ this.serverPortNumber = Args.notNull(serverPortNumber, "serverPortNumber");
+ this.signingCertificateChain = signingCertificateChain;
+ this.signingPrivateKey = signingPrivateKey;
+
+ listenerThread = new RequestListenerThread(this.as2Version, this.originServer, this.serverFqdn, this.serverPortNumber, this.signingCertificateChain, this.signingPrivateKey);
+ listenerThread.setDaemon(true);
+ listenerThread.start();
+ }
+
+ public void close() {
+ if (listenerThread != null) {
+ synchronized (listenerThread) {
+ try {
+ listenerThread.serversocket.close();
+ } catch (IOException e) {
+ LOG.debug(e.getMessage(), e);
+ } finally {
+ listenerThread = null;
+ }
+ }
+ }
+ }
+
+ public void listen(String requestUri, HttpRequestHandler handler) throws IOException {
+ if (listenerThread != null) {
+ synchronized (listenerThread) {
+ listenerThread.registerHandler(requestUri, handler);
+ }
+ }
+ }
+
+ public void stopListening(String requestUri) {
+
+ if (listenerThread != null) {
+ listenerThread.unregisterHandler(requestUri);
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerManager.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerManager.java
new file mode 100644
index 0000000..dfcf973
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerManager.java
@@ -0,0 +1,86 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AS2 Server Manager
+ *
+ * <p>Receives EDI Messages over HTTP
+ *
+ */
+public class AS2ServerManager {
+
+ //
+ // AS2 HTTP Context Attribute Keys
+ //
+
+ /**
+ * Prefix for all AS2 HTTP Context Attributes used by the Http Server
+ * Manager.
+ */
+ public static final String CAMEL_AS2_SERVER_PREFIX = "camel-as2.server";
+
+ /**
+ * The HTTP Context Attribute containing the subject header sent in an AS2
+ * response.
+ */
+ public static final String SUBJECT = CAMEL_AS2_SERVER_PREFIX + "subject";
+
+ /**
+ * The HTTP Context Attribute containing the internet e-mail address of
+ * responding system
+ */
+ public static final String FROM = CAMEL_AS2_SERVER_PREFIX + "from";
+
+ private static final Logger LOG = LoggerFactory.getLogger(AS2ServerManager.class);
+
+ private AS2ServerConnection as2ServerConnection;
+
+ public AS2ServerManager(AS2ServerConnection as2ServerConnection) {
+ this.as2ServerConnection = as2ServerConnection;
+ }
+
+ public void listen(String requestUriPattern, HttpRequestHandler handler) {
+ try {
+ as2ServerConnection.listen(requestUriPattern, handler);
+ } catch (IOException e) {
+ LOG.error("Failed to listen for '" + requestUriPattern + "' requests: " + e.getMessage(), e);
+ throw new RuntimeException("Failed to listen for '" + requestUriPattern + "' requests: " + e.getMessage(), e);
+ }
+
+ }
+
+ public void stopListening(String requestUri) {
+ as2ServerConnection.stopListening(requestUri);
+ }
+
+ public void handleMDNResponse(HttpEntityEnclosingRequest request, HttpResponse response, HttpContext httpContext, String subject, String from) throws HttpException {
+ // Add Context attributes for Response
+ httpContext.setAttribute(SUBJECT, subject);
+ httpContext.setAttribute(FROM, from);
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java
new file mode 100644
index 0000000..791d98f
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java
@@ -0,0 +1,191 @@
+/**
+ * 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.as2.api;
+
+import java.security.Key;
+/**
+ * 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.
+ */
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.http.entity.ContentType;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.cms.CMSAlgorithm;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInformation;
+
+public class AS2SignedDataGenerator extends CMSSignedDataGenerator {
+
+ public static final Map<ASN1ObjectIdentifier, String> STANDARD_MICALGS;
+
+ static {
+ Map<ASN1ObjectIdentifier, String> stdMicAlgs = new HashMap<ASN1ObjectIdentifier, String>();
+
+ stdMicAlgs.put(CMSAlgorithm.MD5, "md5");
+ stdMicAlgs.put(CMSAlgorithm.SHA1, "sha-1");
+ stdMicAlgs.put(CMSAlgorithm.SHA224, "sha-224");
+ stdMicAlgs.put(CMSAlgorithm.SHA256, "sha-256");
+ stdMicAlgs.put(CMSAlgorithm.SHA384, "sha-384");
+ stdMicAlgs.put(CMSAlgorithm.SHA512, "sha-512");
+ stdMicAlgs.put(CMSAlgorithm.GOST3411, "gostr3411-94");
+ stdMicAlgs.put(CMSAlgorithm.GOST3411_2012_256, "gostr3411-2012-256");
+ stdMicAlgs.put(CMSAlgorithm.GOST3411_2012_512, "gostr3411-2012-512");
+
+ STANDARD_MICALGS = Collections.unmodifiableMap(stdMicAlgs);
+ }
+
+ /**
+ * Signing algorithms for DSA keys in order of preference
+ */
+ public static final String[] DSA_SIGNING_ALGORITHMS = {
+ "SHA512WITHDSA",
+ "SHA384WITHDSA",
+ "SHA256WITHDSA",
+ "SHA224WITHDSA",
+ "SHA1WITHDSA",
+ };
+
+ /**
+ * Signing algorithms for RSA keys in order of preference
+ */
+ public static final String[] RSA_SIGNING_ALGORITHMS = {
+ "SHA512WITHRSA",
+ "SHA384WITHRSA",
+ "SHA256WITHRSA",
+ "SHA224WITHRSA",
+ "SHA1WITHRSA",
+ "MD5WITHRSA",
+ "MD2WITHRSA",
+ };
+
+ /**
+ * Signing algorithms for EC keys in order of preference
+ */
+ public static final String[] EC_SIGNING_ALGORITHMS = {
+ "SHA512WITHECDSA",
+ "SHA384WITHECDSA",
+ "SHA256WITHECDSA",
+ "SHA224WITHECDSA",
+ "SHA1WITHECDSA",
+ };
+
+ public AS2SignedDataGenerator() {
+ }
+
+ /**
+ * Creates a <code>multipart/signed</code> content type containing the algorithms used by this generator.
+ *
+ * @return A <code>multipart/signed</code> content type
+ */
+ public ContentType createMultipartSignedContentType(String boundary) {
+ StringBuffer header = new StringBuffer(AS2MediaType.MULTIPART_SIGNED);
+ header.append("; boundary=" + boundary);
+ Set<String> micAlgSet = new HashSet<String>();
+
+ // Collect algorithm names used by pre-calculated signers
+ for (@SuppressWarnings("rawtypes")
+ Iterator it = _signers.iterator(); it.hasNext();) {
+ SignerInformation signer = (SignerInformation) it.next();
+ ASN1ObjectIdentifier digestOID = signer.getDigestAlgorithmID().getAlgorithm();
+
+ String micAlg = (String) STANDARD_MICALGS.get(digestOID);
+
+ if (micAlg == null) {
+ micAlgSet.add("unknown");
+ } else {
+ micAlgSet.add(micAlg);
+ }
+ }
+
+ // Collect algorithm names used by signer generators
+ for (@SuppressWarnings("rawtypes")
+ Iterator it = signerGens.iterator(); it.hasNext();) {
+ SignerInfoGenerator signerInfoGen = (SignerInfoGenerator) it.next();
+ ASN1ObjectIdentifier digestOID = signerInfoGen.getDigestAlgorithm().getAlgorithm();
+
+ String micAlg = (String) STANDARD_MICALGS.get(digestOID);
+
+ if (micAlg == null) {
+ micAlgSet.add("unknown");
+ } else {
+ micAlgSet.add(micAlg);
+ }
+ }
+
+ // Add algorithm names to multipart signed header.
+ int count = 0;
+ for (Iterator<String> it = micAlgSet.iterator(); it.hasNext();) {
+ String alg = it.next();
+
+ if (count == 0) {
+ if (micAlgSet.size() != 1) {
+ header.append("; micalg=\"");
+ } else {
+ header.append("; micalg=");
+ }
+ } else {
+ header.append(',');
+ }
+
+ header.append(alg);
+
+ count++;
+ }
+
+ if (count != 0) {
+ if (micAlgSet.size() != 1) {
+ header.append('\"');
+ }
+ }
+
+ return ContentType.parse(header.toString());
+ }
+
+ public static String[] getSupportedSignatureAlgorithmNamesForKey(Key key) {
+
+ switch (key.getAlgorithm()) {
+ case "DSA":
+ return DSA_SIGNING_ALGORITHMS;
+ case "RSA":
+ return RSA_SIGNING_ALGORITHMS;
+ case "EC":
+ return EC_SIGNING_ALGORITHMS;
+ default:
+ return new String[0];
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2TransferEncoding.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2TransferEncoding.java
new file mode 100644
index 0000000..801c63e
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2TransferEncoding.java
@@ -0,0 +1,23 @@
+/**
+ * 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.as2.api;
+
+public interface AS2TransferEncoding {
+ public static final String NONE = null;
+ public static final String BASE64 = "base64";
+ public static final String SEVENBIT = "7bit";
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/CanonicalOutputStream.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/CanonicalOutputStream.java
new file mode 100644
index 0000000..e926422
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/CanonicalOutputStream.java
@@ -0,0 +1,80 @@
+/**
+ * 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.as2.api;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class CanonicalOutputStream extends FilterOutputStream {
+
+ private static byte[] newline;
+ private int lastByte;
+
+ static {
+ newline = new byte[2];
+ newline[0] = (byte) '\r';
+ newline[1] = (byte) '\n';
+ }
+
+ private String charset;
+
+ public CanonicalOutputStream(OutputStream out, String charset) {
+ super(out);
+ this.charset = charset;
+ lastByte = -1;
+ }
+
+ public void write(int i) throws IOException {
+ if (i == '\r') {
+ // convert all carriage-return characters into line-break sequence
+ out.write(newline);
+ } else if (i == '\n') {
+ // convert line-feed character into line-break sequence if not
+ // preceded by carriage-return
+ if (lastByte != '\r') {
+ out.write(newline);
+ }
+ // otherwise the line-feed was preceded by carriage-return so ignore it.
+ } else {
+ out.write(i);
+ }
+
+ lastByte = i;
+ }
+
+ public void write(byte[] buf) throws IOException {
+ this.write(buf, 0, buf.length);
+ }
+
+ public void write(byte buf[], int off, int len) throws IOException {
+ for (int i = off; i != off + len; i++) {
+ this.write(buf[i]);
+ }
+ }
+
+ public void writeln(String s) throws IOException {
+ byte[] bytes = s.getBytes(charset);
+ write(bytes);
+ write(newline);
+ }
+
+ public void writeln() throws IOException {
+ write(newline);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/InvalidAS2NameException.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/InvalidAS2NameException.java
new file mode 100644
index 0000000..6439fdc
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/InvalidAS2NameException.java
@@ -0,0 +1,73 @@
+/**
+ * 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.as2.api;
+
+/**
+ * Thrown to indicated a given AS2 name is invalid.
+ */
+public class InvalidAS2NameException extends Exception {
+
+ private static final long serialVersionUID = -6284079291785073089L;
+
+ private final String name;
+
+ private final int index;
+
+ /**
+ * Constructs an <code>InvalidAS2NameException</code> for the
+ * specified name and index.
+ *
+ * @param name - the AS2 name that is invalid.
+ * @param index - the index in the <code>name</code> of the invalid character
+ */
+ public InvalidAS2NameException(String name, int index) {
+ super();
+ this.name = name;
+ this.index = index;
+ }
+
+ /* (non-Javadoc)
+ * @see java.lang.Throwable#getMessage()
+ */
+ @Override
+ public String getMessage() {
+ char character = name.charAt(index);
+ String invalidChar = "" + character;
+ if (Character.isISOControl(character)) {
+ invalidChar = String.format("\\u%04x", (int) character);
+ }
+ return "Invalid character '" + invalidChar + "' at index " + index;
+ }
+
+ /**
+ * Returns the invalid AS2 name
+ *
+ * @return the invalid AS2 name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the index of the invalid character in <code>name</code>
+ *
+ * @return the index of the invalid character in <code>name</code>
+ */
+ public int getIndex() {
+ return index;
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/MDNField.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/MDNField.java
new file mode 100644
index 0000000..6352740
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/MDNField.java
@@ -0,0 +1,66 @@
+/**
+ * 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.as2.api;
+
+public interface MDNField {
+
+ /**
+ * Field Name for Reporting UA
+ */
+ public static final String REPORTING_UA = "Reporting-UA";
+
+ /**
+ * Field Name for MDN Gateway
+ */
+ public static final String MDN_GATEWAY = "MDN-Gateway";
+
+ /**
+ * Field Name for Final Recipient
+ */
+ public static final String FINAL_RECIPIENT = "Final-Recipient";
+
+ /**
+ * Field Name for Original Message IDX
+ */
+ public static final String ORIGINAL_MESSAGE_ID = "Original-Message-ID";
+
+ /**
+ * Field Name for Disposition
+ */
+ public static final String DISPOSITION = "Disposition";
+
+ /**
+ * Field Name for Failure
+ */
+ public static final String FAILURE = "Failure";
+
+ /**
+ * Field Name for Error
+ */
+ public static final String ERROR = "Error";
+
+ /**
+ * Field Name for Warning
+ */
+ public static final String WARNING = "Warning";
+
+ /**
+ * Field Name for Received Content MIC
+ */
+ public static final String RECEIVED_CONTENT_MIC = "Received-content-MIC";
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/Util.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/Util.java
new file mode 100644
index 0000000..ce1c613
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/Util.java
@@ -0,0 +1,183 @@
+/**
+ * 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.as2.api;
+
+import java.awt.event.KeyEvent;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpMessage;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.RequestLine;
+import org.apache.http.StatusLine;
+
+/**
+ * Utility Methods used in AS2 Component
+ */
+public final class Util {
+
+ public static final String DQUOTE = "\"";
+ public static final String BACKSLASH = "\\\\";
+ public static final String AS2_TEXT_CHAR_SET = "[\u0021\u0023-\\\u005B\\\u005D-\u007E]";
+ public static final String AS2_QUOTED_TEXT_CHAR_SET = "[\u0020\u0021\u0023-\\\u005B\\\u005D-\u007E]";
+ public static final String AS2_QUOTED_PAIR = BACKSLASH + DQUOTE + "|" + BACKSLASH + BACKSLASH;
+
+ public static final String AS2_QUOTED_NAME = DQUOTE + "(" + AS2_QUOTED_TEXT_CHAR_SET + "|" + AS2_QUOTED_PAIR + "){1,128}" + DQUOTE;
+ public static final String AS2_ATOMIC_NAME = "(" + AS2_TEXT_CHAR_SET + "){1,128}";
+ public static final String AS2_NAME = AS2_ATOMIC_NAME + "|" + AS2_QUOTED_NAME;
+
+ public static final Pattern AS_NAME_PATTERN = Pattern.compile(AS2_NAME);
+
+ private static Random generator = new Random();
+
+ private Util() {
+ }
+
+ /**
+ * Validates if the given <code>name</code> is a valid AS2 Name
+ *
+ * @param name - the name to validate.
+ * @throws InvalidAS2NameException
+ */
+ public static void validateAS2Name(String name) throws InvalidAS2NameException {
+ Matcher matcher = AS_NAME_PATTERN.matcher(name);
+ if (!matcher.matches()) {
+ // if name does not match, determine where it fails to match.
+ int i = 0;
+ for (i = name.length() - 1; i > 0; i--) {
+ Matcher region = matcher.region(0, i);
+ if (region.matches() || region.hitEnd()) {
+ break;
+ }
+ }
+ throw new InvalidAS2NameException(name, i);
+ }
+ }
+
+ /**
+ * Generates a globally unique message ID which includes <code>fqdn</code>: a fully qualified domain name (FQDN)
+ * @param fqdn - the fully qualified domain name to use in message id.
+ * @return The generated message id.
+ */
+ public static String createMessageId(String fqdn) {
+ /* Wall Clock Time in Nanoseconds */ /* 64 Bit Random Number */ /* Fully Qualified Domain Name */
+ return "<" + Long.toString(System.nanoTime(), 36) + "." + Long.toString(generator.nextLong(), 36) + "@" + fqdn + ">";
+ }
+
+
+ /**
+ * Determines if <code>c</code> is a printable character.
+ * @param c - the character to test
+ * @return <code>true</code> if <code>c</code> is a printable character; <code>false</code> otherwise.
+ */
+ public static boolean isPrintableChar(char c) {
+ Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
+ return (!Character.isISOControl(c)) && c != KeyEvent.CHAR_UNDEFINED && block != null
+ && block != Character.UnicodeBlock.SPECIALS;
+ }
+
+ public static String printRequest(HttpRequest request) throws IOException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true, "utf-8")) {
+ printRequest(ps, request);
+ String content = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ return content;
+ }
+ }
+
+ public static String printMessage(HttpMessage message) throws IOException {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos, true, "utf-8")) {
+ printMessage(ps, message);
+ String content = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ return content;
+ }
+ }
+
+ /**
+ * Prints the contents of request to given print stream.
+ *
+ * @param out
+ * - the stream printed to.
+ * @param request
+ * - the request printed.
+ * @throws IOException
+ */
+ public static void printRequest(PrintStream out, HttpRequest request) throws IOException {
+ // Print request line
+ RequestLine requestLine = request.getRequestLine();
+ out.println(requestLine.getMethod() + ' ' + requestLine.getUri() + ' ' + requestLine.getProtocolVersion());
+
+ // Write headers
+ for (final HeaderIterator it = request.headerIterator(); it.hasNext();) {
+ Header header = it.nextHeader();
+ out.println(header.getName() + ": " + (header.getValue() == null ? "" : header.getValue()));
+ }
+ out.println(); // write empty line separating header from body.
+
+ if (request instanceof HttpEntityEnclosingRequest) {
+ // Write entity
+ HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
+ entity.writeTo(out);
+ }
+ }
+
+ /**
+ * Prints the contents of an Http Message to given print stream.
+ *
+ * @param out - the stream printed to.
+ * @param message - the request printed.
+ * @throws IOException
+ */
+ public static void printMessage(PrintStream out, HttpMessage message) throws IOException {
+ // Print request line
+ if (message instanceof HttpRequest) {
+ RequestLine requestLine = ((HttpRequest)message).getRequestLine();
+ out.println(requestLine.getMethod() + ' ' + requestLine.getUri() + ' ' + requestLine.getProtocolVersion());
+ } else { // HttpResponse
+ StatusLine statusLine = ((HttpResponse)message).getStatusLine();
+ out.println(statusLine.toString());
+ }
+ // Write headers
+ for (final HeaderIterator it = message.headerIterator(); it.hasNext();) {
+ Header header = it.nextHeader();
+ out.println(header.getName() + ": " + (header.getValue() == null ? "" : header.getValue()));
+ }
+ out.println(); // write empty line separating header from body.
+
+ if (message instanceof HttpEntityEnclosingRequest) {
+ // Write entity
+ HttpEntity entity = ((HttpEntityEnclosingRequest)message).getEntity();
+ entity.writeTo(out);
+ } else if (message instanceof HttpResponse) {
+ // Write entity
+ HttpEntity entity = ((HttpResponse)message).getEntity();
+ entity.writeTo(out);
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionModifier.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionModifier.java
new file mode 100644
index 0000000..b4ed117
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionModifier.java
@@ -0,0 +1,91 @@
+/**
+ * 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.as2.api.entity;
+
+public final class AS2DispositionModifier {
+
+ public static final AS2DispositionModifier ERROR = new AS2DispositionModifier("error");
+ public static final AS2DispositionModifier ERROR_AUTHENTICATION_FAILED = new AS2DispositionModifier("error: authentication-failed");
+ public static final AS2DispositionModifier ERROR_DECOMPRESSION_FAILED = new AS2DispositionModifier("error: decompression-failed");
+ public static final AS2DispositionModifier ERROR_DECRYPTION_FAILED = new AS2DispositionModifier("error: decryption-failed");
+ public static final AS2DispositionModifier ERROR_INSUFFICIENT_MESSAGE_SECURITY = new AS2DispositionModifier("error: insufficient-message-security");
+ public static final AS2DispositionModifier ERROR_INTEGRITY_CHECK_FAILED = new AS2DispositionModifier("error: integrity-check-failed");
+ public static final AS2DispositionModifier ERROR_UNEXPECTED_PROCESSING_ERROR = new AS2DispositionModifier("error: unexpected-processing-error");
+ public static final AS2DispositionModifier WARNING = new AS2DispositionModifier("warning");
+
+ private String modifier;
+
+ private AS2DispositionModifier(String modifier) {
+ this.modifier = modifier;
+ }
+
+ public String getModifier() {
+ return modifier;
+ }
+
+ public boolean isError() {
+ return modifier.startsWith("error: ");
+ }
+
+ public boolean isFailuer() {
+ return modifier.startsWith("failure: ");
+ }
+
+ public boolean isWarning() {
+ return modifier.startsWith("warning: ");
+ }
+
+ @Override
+ public String toString() {
+ return modifier;
+ }
+
+ public static AS2DispositionModifier createWarning(String description) {
+ return new AS2DispositionModifier("warning: " + description);
+ }
+
+ public static AS2DispositionModifier createFailure(String description) {
+ return new AS2DispositionModifier("failure: " + description);
+ }
+
+ public static AS2DispositionModifier parseDispositionType(String dispositionModifierString) {
+ switch(dispositionModifierString) {
+ case "error":
+ return ERROR;
+ case "error: authentication-failed":
+ return ERROR_AUTHENTICATION_FAILED;
+ case "error: decompression-failed\"":
+ return ERROR_DECOMPRESSION_FAILED;
+ case "error: decryption-failed":
+ return ERROR_DECRYPTION_FAILED;
+ case "error: insufficient-message-security":
+ return ERROR_INSUFFICIENT_MESSAGE_SECURITY;
+ case "error: integrity-check-failed":
+ return ERROR_INTEGRITY_CHECK_FAILED;
+ case "error: unexpected-processing-error":
+ return ERROR_UNEXPECTED_PROCESSING_ERROR;
+ case "warning":
+ return WARNING;
+ default:
+ if (dispositionModifierString.startsWith("warning: ") || dispositionModifierString.startsWith("failure: ")) {
+ return new AS2DispositionModifier(dispositionModifierString);
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionType.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionType.java
new file mode 100644
index 0000000..13b1fff
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2DispositionType.java
@@ -0,0 +1,48 @@
+/**
+ * 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.as2.api.entity;
+
+public enum AS2DispositionType {
+ PROCESSED("processed"),
+ FAILED("failed");
+
+ private String type;
+
+ private AS2DispositionType(String type) {
+ this.type = type;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ return type;
+ }
+
+ public static AS2DispositionType parseDispositionType(String dispositionTypeString) {
+ switch(dispositionTypeString) {
+ case "processed":
+ return PROCESSED;
+ case "failed":
+ return FAILED;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
new file mode 100644
index 0000000..b1a4bf2
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
@@ -0,0 +1,262 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.camel.component.as2.api.util.MicUtils;
+import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.util.Args;
+
+public class AS2MessageDispositionNotificationEntity extends MimeEntity {
+
+ private static final String ADDRESS_TYPE_PREFIX = "rfc822;";
+ private static final String MTA_NAME_TYPE_PREFIX = "dns;";
+ private static final String REPORTING_UA = "Reporting-UA";
+ private static final String MDN_GATEWAY = "MDN-Gateway";
+ private static final String FINAL_RECIPIENT = "Final-Recipient";
+ private static final String ORIGINAL_MESSAGE_ID = "Original-Message-ID";
+ private static final String AS2_DISPOSITION = "Disposition";
+ private static final String FAILURE = "Failure";
+ private static final String ERROR = "Error";
+ private static final String WARNING = "Warning";
+ private static final String RECEIVED_CONTENT_MIC = "Received-content-MIC";
+
+ private String reportingUA;
+ // TODO determine if we need to support this field.
+ private String mtnName;
+ private String finalRecipient;
+ private String originalMessageId;
+ private DispositionMode dispositionMode;
+ private AS2DispositionType dispositionType;
+ private AS2DispositionModifier dispositionModifier;
+ private String[] failureFields;
+ private String[] errorFields;
+ private String[] warningFields;
+ private Map<String, String> extensionFields = new HashMap<String, String>();
+ private ReceivedContentMic receivedContentMic;
+
+ public AS2MessageDispositionNotificationEntity(HttpEntityEnclosingRequest request,
+ HttpResponse response,
+ DispositionMode dispositionMode,
+ AS2DispositionType dispositionType,
+ AS2DispositionModifier dispositionModifier,
+ String[] failureFields,
+ String[] errorFields,
+ String[] warningFields,
+ Map<String, String> extensionFields,
+ String charset,
+ boolean isMainBody) throws HttpException {
+ setMainBody(isMainBody);
+ setContentType(ContentType.create(AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION, charset));
+
+ this.finalRecipient = HttpMessageUtils.getHeaderValue(request, AS2Header.AS2_TO);
+ if (this.finalRecipient == null) {
+ throw new HttpException("The " + AS2Header.AS2_TO + " is missing");
+ }
+
+ this.originalMessageId = HttpMessageUtils.getHeaderValue(request, AS2Header.MESSAGE_ID);
+
+ this.receivedContentMic = MicUtils.createReceivedContentMic(request);
+
+ this.reportingUA = HttpMessageUtils.getHeaderValue(response, AS2Header.SERVER);
+
+ this.dispositionMode = Args.notNull(dispositionMode, "Disposition Mode");
+ this.dispositionType = Args.notNull(dispositionType, "Disposition Type");
+ this.dispositionModifier = dispositionModifier;
+ this.failureFields = failureFields;
+ this.errorFields = errorFields;
+ this.warningFields = warningFields;
+ if (extensionFields == null || extensionFields.isEmpty()) {
+ this.extensionFields.clear();
+ } else {
+ this.extensionFields.putAll(extensionFields);
+ }
+ }
+
+ public AS2MessageDispositionNotificationEntity(String reportingUA,
+ String mtnName,
+ String finalRecipient,
+ String originalMessageId,
+ DispositionMode dispositionMode,
+ AS2DispositionType dispositionType,
+ AS2DispositionModifier dispositionModifier,
+ String[] failureFields,
+ String[] errorFields,
+ String[] warningFields,
+ Map<String, String> extensionFields,
+ ReceivedContentMic receivedContentMic) {
+ super();
+ this.reportingUA = reportingUA;
+ this.mtnName = mtnName;
+ this.finalRecipient = finalRecipient;
+ this.originalMessageId = originalMessageId;
+ this.dispositionMode = dispositionMode;
+ this.dispositionType = dispositionType;
+ this.dispositionModifier = dispositionModifier;
+ this.failureFields = failureFields;
+ this.errorFields = errorFields;
+ this.warningFields = warningFields;
+ this.extensionFields = extensionFields;
+ this.receivedContentMic = receivedContentMic;
+ }
+
+ public String getReportingUA() {
+ return reportingUA;
+ }
+
+ public String getMtnName() {
+ return mtnName;
+ }
+
+ public String getFinalRecipient() {
+ return finalRecipient;
+ }
+
+ public String getOriginalMessageId() {
+ return originalMessageId;
+ }
+
+ public DispositionMode getDispositionMode() {
+ return dispositionMode;
+ }
+
+ public AS2DispositionType getDispositionType() {
+ return dispositionType;
+ }
+
+ public AS2DispositionModifier getDispositionModifier() {
+ return dispositionModifier;
+ }
+
+ public String[] getFailureFields() {
+ return failureFields;
+ }
+
+ public String[] getErrorFields() {
+ return errorFields;
+ }
+
+ public String[] getWarningFields() {
+ return warningFields;
+ }
+
+ public Map<String, String> getExtensionFields() {
+ return extensionFields;
+ }
+
+ public ReceivedContentMic getReceivedContentMic() {
+ return receivedContentMic;
+ }
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ NoCloseOutputStream ncos = new NoCloseOutputStream(outstream);
+ try (CanonicalOutputStream canonicalOutstream = new CanonicalOutputStream(ncos, AS2Charset.US_ASCII)) {
+
+ // Write out mime part headers if this is not the main body of
+ // message.
+ if (!isMainBody()) {
+ HeaderIterator it = headerIterator();
+ while (it.hasNext()) {
+ Header header = it.nextHeader();
+ canonicalOutstream.writeln(header.toString());
+ }
+ canonicalOutstream.writeln(); // ensure empty line between
+ // headers and body; RFC2046 -
+ // 5.1.1
+ }
+
+ if (reportingUA != null) {
+ Header reportingUAField = new BasicHeader(REPORTING_UA, reportingUA);
+ canonicalOutstream.writeln(reportingUAField.toString());
+ }
+
+ if (mtnName != null) {
+ Header mdnGatewayField = new BasicHeader(MDN_GATEWAY, MTA_NAME_TYPE_PREFIX + mtnName);
+ canonicalOutstream.writeln(mdnGatewayField.toString());
+ }
+
+ Header finalRecipientField = new BasicHeader(FINAL_RECIPIENT, ADDRESS_TYPE_PREFIX + finalRecipient);
+ canonicalOutstream.writeln(finalRecipientField.toString());
+
+ if (originalMessageId != null) {
+ Header originalMessageIdField = new BasicHeader(ORIGINAL_MESSAGE_ID, originalMessageId);
+ canonicalOutstream.writeln(originalMessageIdField.toString());
+ }
+
+ String as2Disposition = dispositionMode.toString() + ";" + dispositionType.toString();
+ if (dispositionModifier != null) {
+ as2Disposition = as2Disposition + "/" + dispositionModifier.toString();
+ }
+ Header as2DispositionField = new BasicHeader(AS2_DISPOSITION,
+ dispositionMode.toString() + ";" + dispositionType.toString());
+ canonicalOutstream.writeln(as2DispositionField.toString());
+
+ if (failureFields != null) {
+ for (String field : failureFields) {
+ Header failureField = new BasicHeader(FAILURE, field);
+ canonicalOutstream.writeln(failureField.toString());
+ }
+ }
+
+ if (errorFields != null) {
+ for (String field : errorFields) {
+ Header errorField = new BasicHeader(ERROR, field);
+ canonicalOutstream.writeln(errorField.toString());
+ }
+ }
+
+ if (failureFields != null) {
+ for (String field : failureFields) {
+ Header failureField = new BasicHeader(WARNING, field);
+ canonicalOutstream.writeln(failureField.toString());
+ }
+ }
+
+ if (extensionFields != null) {
+ for (Entry<String, String> entry : extensionFields.entrySet()) {
+ Header failureField = new BasicHeader(entry.getKey(), entry.getValue());
+ canonicalOutstream.writeln(failureField.toString());
+ }
+ }
+
+ if (receivedContentMic != null) {
+ Header as2ReceivedContentMicField = new BasicHeader(RECEIVED_CONTENT_MIC,
+ receivedContentMic.toString());
+ canonicalOutstream.writeln(as2ReceivedContentMicField.toString());
+ }
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIConsentEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIConsentEntity.java
new file mode 100644
index 0000000..3b25e6a
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIConsentEntity.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.http.entity.ContentType;
+
+public class ApplicationEDIConsentEntity extends ApplicationEDIEntity {
+
+ public ApplicationEDIConsentEntity(String content, String charset, String contentTransferEncoding,
+ boolean isMainBody) {
+ super(content, ContentType.create(AS2MediaType.APPLICATION_EDI_CONSENT, charset), contentTransferEncoding, isMainBody);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIEntity.java
new file mode 100644
index 0000000..a49396a
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIEntity.java
@@ -0,0 +1,64 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.Args;
+
+public abstract class ApplicationEDIEntity extends MimeEntity {
+
+ private final String ediMessage;
+
+ protected ApplicationEDIEntity(String ediMessage, ContentType contentType, String contentTransferEncoding, boolean isMainBody) {
+ this.ediMessage = Args.notNull(ediMessage, "EDI Message");
+ setContentType(Args.notNull(contentType, "Content Type").toString());
+ setContentTransferEncoding(contentTransferEncoding);
+ setMainBody(isMainBody);
+ }
+
+ public String getEdiMessage() {
+ return ediMessage;
+ }
+
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ NoCloseOutputStream ncos = new NoCloseOutputStream(outstream);
+ try (CanonicalOutputStream canonicalOutstream = new CanonicalOutputStream(ncos, AS2Charset.US_ASCII)) {
+
+ // Write out mime part headers if this is not the main body of message.
+ if (!isMainBody()) {
+ HeaderIterator it = headerIterator();
+ while (it.hasNext()) {
+ Header header = it.nextHeader();
+ canonicalOutstream.writeln(header.toString());
+ }
+ canonicalOutstream.writeln(); // ensure empty line between headers and body; RFC2046 - 5.1.1
+ }
+
+ canonicalOutstream.write(ediMessage.getBytes(getCharset()), 0, ediMessage.length());
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIFACTEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIFACTEntity.java
new file mode 100644
index 0000000..4e94456
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIFACTEntity.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.http.entity.ContentType;
+
+public class ApplicationEDIFACTEntity extends ApplicationEDIEntity {
+
+ public ApplicationEDIFACTEntity(String content, String charset, String contentTransferEncoding,
+ boolean isMainBody) {
+ super(content, ContentType.create(AS2MediaType.APPLICATION_EDIFACT, charset), contentTransferEncoding, isMainBody);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIX12Entity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIX12Entity.java
new file mode 100644
index 0000000..24f1702
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationEDIX12Entity.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.http.entity.ContentType;
+
+public class ApplicationEDIX12Entity extends ApplicationEDIEntity {
+
+ public ApplicationEDIX12Entity(String content, String charset, String contentTransferEncoding,
+ boolean isMainBody) {
+ super(content, ContentType.create(AS2MediaType.APPLICATION_EDI_X12, charset), contentTransferEncoding, isMainBody);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7SignatureEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7SignatureEntity.java
new file mode 100644
index 0000000..2b78f6e
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7SignatureEntity.java
@@ -0,0 +1,125 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.HttpException;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.Args;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.CMSTypedData;
+
+public class ApplicationPkcs7SignatureEntity extends MimeEntity {
+
+ private static final String CONTENT_DISPOSITION = "attachment; filename=\"smime.p7s\"";
+
+ private static final String CONTENT_DESCRIPTION = "S/MIME Cryptographic Signature";
+
+ private byte[] signature;
+
+ public ApplicationPkcs7SignatureEntity(MimeEntity data, CMSSignedDataGenerator signer, String charset, String contentTransferEncoding, boolean isMainBody) throws HttpException {
+ Args.notNull(data, "Data");
+ Args.notNull(signer, "Signer");
+
+ ContentType contentType = ContentType.parse(EntityUtils.appendParameter(AS2MediaType.APPLICATION_PKCS7_SIGNATURE, "charset", charset));
+ setContentType(contentType.toString());
+ setContentTransferEncoding(contentTransferEncoding);
+ addHeader(AS2Header.CONTENT_DISPOSITION, CONTENT_DISPOSITION);
+ addHeader(AS2Header.CONTENT_DESCRIPTION, CONTENT_DESCRIPTION);
+ setMainBody(isMainBody);
+ try {
+ this.signature = createSignature(data, signer);
+ } catch (Exception e) {
+ throw new HttpException("Failed to create signed data", e);
+ }
+ }
+
+ public ApplicationPkcs7SignatureEntity(String charset,
+ String contentTransferEncoding,
+ byte[] signature,
+ boolean isMainBody)
+ throws HttpException {
+ this.signature = signature;
+ ContentType contentType = ContentType
+ .parse(EntityUtils.appendParameter(AS2MediaType.APPLICATION_PKCS7_SIGNATURE, "charset", charset));
+ setContentType(contentType.toString());
+ setContentTransferEncoding(contentTransferEncoding);
+ addHeader(AS2Header.CONTENT_DISPOSITION, CONTENT_DISPOSITION);
+ addHeader(AS2Header.CONTENT_DESCRIPTION, CONTENT_DESCRIPTION);
+ setMainBody(isMainBody);
+ }
+
+ public byte[] getSignature() {
+ return signature;
+ }
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ NoCloseOutputStream ncos = new NoCloseOutputStream(outstream);
+
+ // Write out mime part headers if this is not the main body of message.
+ if (!isMainBody()) {
+ try (CanonicalOutputStream canonicalOutstream = new CanonicalOutputStream(ncos, AS2Charset.US_ASCII)) {
+
+ HeaderIterator it = headerIterator();
+ while (it.hasNext()) {
+ Header header = it.nextHeader();
+ canonicalOutstream.writeln(header.toString());
+ }
+ canonicalOutstream.writeln(); // ensure empty line between
+ // headers and body; RFC2046 -
+ // 5.1.1
+ }
+ }
+
+ // Write out signed data.
+ String transferEncoding = getContentTransferEncoding() == null ? null : getContentTransferEncoding().getValue();
+ try (OutputStream trasnsferEncodedStream = EntityUtils.encode(ncos, transferEncoding)) {
+
+ trasnsferEncodedStream.write(signature);
+ } catch (Exception e) {
+ throw new IOException("Failed to write to output stream", e);
+ }
+ }
+
+ private byte[] createSignature(MimeEntity data, CMSSignedDataGenerator signer) throws Exception {
+ try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+ data.writeTo(bos);
+ bos.flush();
+
+ CMSTypedData contentData = new CMSProcessableByteArray(bos.toByteArray());
+ CMSSignedData signedData = signer.generate(contentData, false);
+ return signedData.getEncoded();
+ } catch (Exception e) {
+ throw new Exception("", e);
+ }
+
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionMode.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionMode.java
new file mode 100644
index 0000000..4ddb6c6
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionMode.java
@@ -0,0 +1,60 @@
+/**
+ * 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.as2.api.entity;
+
+public enum DispositionMode {
+ MANUAL_ACTION_MDN_SENT_MANUALLY("manual-action", "MDN-sent-manually"),
+ MANUAL_ACTION_MDN_SENT_AUTOMATICALLY("manual-action", "MDN-sent-automatically"),
+ AUTOMATIC_ACTION_MDN_SENT_MANUALLY("automatic-action", "MDN-sent-manually"),
+ AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY("automatic-action", "MDN-sent-automatically");
+
+ private String actionMode;
+ private String sendingMode;
+
+ private DispositionMode(String actionMode, String sendingMode) {
+ this.actionMode = actionMode;
+ this.sendingMode = sendingMode;
+ }
+
+ public String getActionMode() {
+ return actionMode;
+ }
+
+ public String getSendingMode() {
+ return sendingMode;
+ }
+
+ @Override
+ public String toString() {
+ return actionMode + "/" + sendingMode;
+ }
+
+ public static DispositionMode parseDispositionMode(String dispositionModeString) {
+ switch(dispositionModeString) {
+ case "manual-action/MDN-sent-manually":
+ return MANUAL_ACTION_MDN_SENT_MANUALLY;
+ case "manual-actionMDN-sent-automatically":
+ return MANUAL_ACTION_MDN_SENT_AUTOMATICALLY;
+ case "automatic-action/MDN-sent-manually":
+ return AUTOMATIC_ACTION_MDN_SENT_MANUALLY;
+ case "automatic-action/MDN-sent-automatically":
+ return AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
new file mode 100644
index 0000000..30a8023
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
@@ -0,0 +1,101 @@
+/**
+ * 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.as2.api.entity;
+
+import java.util.Map;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.AS2TransferEncoding;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.util.CharArrayBuffer;
+
+public class DispositionNotificationMultipartReportEntity extends MultipartReportEntity {
+
+ public DispositionNotificationMultipartReportEntity(HttpEntityEnclosingRequest request,
+ HttpResponse response,
+ DispositionMode dispositionMode,
+ AS2DispositionType dispositionType,
+ AS2DispositionModifier dispositionModifier,
+ String[] failureFields,
+ String[] errorFields,
+ String[] warningFields,
+ Map<String, String> extensionFields,
+ String charset,
+ String boundary,
+ boolean isMainBody)
+ throws HttpException {
+ super(charset, isMainBody, boundary);
+ this.contentType = new BasicHeader(AS2Header.CONTENT_TYPE, AS2MimeType.MULTIPART_REPORT);
+ Header reportType = new BasicHeader(AS2Header.REPORT_TYPE, getReportTypeValue(boundary));
+ addHeader(reportType);
+
+ addPart(buildPlainTextReport(request, response, dispositionMode, dispositionType,
+ dispositionModifier, failureFields, errorFields, warningFields, extensionFields));
+ addPart(new AS2MessageDispositionNotificationEntity(request, response, dispositionMode, dispositionType,
+ dispositionModifier, failureFields, errorFields, warningFields, extensionFields, charset, false));
+ }
+
+ protected DispositionNotificationMultipartReportEntity(String boundary, boolean isMainBody) {
+ this.boundary = boundary;
+ this.isMainBody = isMainBody;
+ }
+
+ protected TextPlainEntity buildPlainTextReport(HttpEntityEnclosingRequest request,
+ HttpResponse response,
+ DispositionMode dispositionMode,
+ AS2DispositionType dispositionType,
+ AS2DispositionModifier dispositionModifier,
+ String[] failureFields,
+ String[] errorFields,
+ String[] warningFields,
+ Map<String, String> extensionFields) throws HttpException {
+
+ CharArrayBuffer charBuffer = new CharArrayBuffer(10);
+
+ String originalMessageId = HttpMessageUtils.getHeaderValue(request, AS2Header.MESSAGE_ID);
+ String sentDate = HttpMessageUtils.getHeaderValue(request, AS2Header.DATE);
+ String subject = HttpMessageUtils.getHeaderValue(request, AS2Header.SUBJECT);
+
+ String receivedFrom = HttpMessageUtils.getHeaderValue(request, AS2Header.AS2_FROM);
+ String sentTo = HttpMessageUtils.getHeaderValue(request, AS2Header.AS2_TO);
+
+ String receivedDate = HttpMessageUtils.getHeaderValue(response, AS2Header.DATE);
+
+ charBuffer.append("MDN for -\n");
+ charBuffer.append(" Message ID: " + originalMessageId + "\n");
+ charBuffer.append(" Subject: " + (subject == null ? "" : subject) + "\n");
+ charBuffer.append(" Date: " + (sentDate == null ? "" : sentDate) + "\n");
+ charBuffer.append(" From: " + receivedFrom + "\n");
+ charBuffer.append(" To: " + sentTo + "\n");
+ charBuffer.append(" Received on: " + receivedDate + "\n");
+ charBuffer.append(" Status: " + dispositionType + "\n");
+
+ return new TextPlainEntity(charBuffer.toString(), AS2Charset.US_ASCII, AS2TransferEncoding.SEVENBIT, false);
+ }
+
+ protected String getReportTypeValue(String boundary) {
+ return "disposition-notification; boundary=\"" + boundary + "\"";
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptions.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptions.java
new file mode 100644
index 0000000..ca9c628
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptions.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils.Parameter;
+
+public class DispositionNotificationOptions {
+
+ private Parameter signedReceiptProtocol;
+ private Parameter signedReceiptMicalg;
+
+ public DispositionNotificationOptions(Parameter signedReceiptProtocol, Parameter signedReceiptMicalg) {
+ this.signedReceiptProtocol = signedReceiptProtocol;
+ this.signedReceiptMicalg = signedReceiptMicalg;
+ }
+
+ public Parameter getSignedReceiptProtocol() {
+ return signedReceiptProtocol;
+ }
+
+ public Parameter getSignedReceiptMicalg() {
+ return signedReceiptMicalg;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParser.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParser.java
new file mode 100644
index 0000000..5688c58
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParser.java
@@ -0,0 +1,69 @@
+/**
+ * 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.as2.api.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils;
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils.Parameter;
+import org.apache.http.ParseException;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.util.Args;
+import org.apache.http.util.CharArrayBuffer;
+
+public class DispositionNotificationOptionsParser {
+
+ public static final DispositionNotificationOptionsParser INSTANCE = new DispositionNotificationOptionsParser();
+
+ private static final String SIGNED_RECEIPT_PROTOCOL_ATTR_NAME = "signed-receipt-protocol";
+ private static final String SIGNED_RECEIPT_MICALG_ATTR_NAME = "signed-receipt-micalg";
+
+ public static DispositionNotificationOptions parseDispositionNotificationOptions(final String value,
+ DispositionNotificationOptionsParser parser)
+ throws ParseException {
+ if (value == null) {
+ return new DispositionNotificationOptions(null, null);
+ }
+
+ final CharArrayBuffer buffer = new CharArrayBuffer(value.length());
+ buffer.append(value);
+ final ParserCursor cursor = new ParserCursor(0, value.length());
+ return (parser != null ? parser : DispositionNotificationOptionsParser.INSTANCE)
+ .parseDispositionNotificationOptions(buffer, cursor);
+ }
+
+ public DispositionNotificationOptions parseDispositionNotificationOptions(final CharArrayBuffer buffer,
+ final ParserCursor cursor) {
+ Args.notNull(buffer, "buffer");
+ Args.notNull(cursor, "cursor");
+
+ Map<String, Parameter> parameters = new HashMap<String, Parameter>();
+ while (!cursor.atEnd()) {
+ Parameter parameter = AS2HeaderUtils.parseParameter(buffer, cursor);
+ parameters.put(parameter.getAttribute(), parameter);
+ }
+
+ Parameter signedReceiptProtocolParameter = parameters.get(SIGNED_RECEIPT_PROTOCOL_ATTR_NAME);
+
+ Parameter signedReceiptMicalgParameter = parameters.get(SIGNED_RECEIPT_MICALG_ATTR_NAME);
+
+
+ return new DispositionNotificationOptions(signedReceiptProtocolParameter, signedReceiptMicalgParameter);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
new file mode 100644
index 0000000..28d0715
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
@@ -0,0 +1,854 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.io.AS2SessionInputBuffer;
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils;
+import org.apache.camel.component.as2.api.util.ContentTypeUtils;
+import org.apache.camel.component.as2.api.util.DispositionNotificationContentUtils;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpMessage;
+import org.apache.http.ParseException;
+import org.apache.http.entity.ContentType;
+import org.apache.http.impl.io.AbstractMessageParser;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.impl.io.SessionInputBufferImpl;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.message.LineParser;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.util.Args;
+import org.apache.http.util.CharArrayBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class EntityParser {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EntityParser.class);
+
+ private static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+ private static final String APPLICATION_EDI_CONTENT_TYPE_PREFIX = "application/edi";
+
+
+ private EntityParser() {
+ }
+
+ public static boolean isBoundaryCloseDelimiter(final CharArrayBuffer buffer, ParserCursor cursor, String boundary) {
+ Args.notNull(buffer, "Buffer");
+ Args.notNull(boundary, "Boundary");
+
+ String boundaryCloseDelimiter = "--" + boundary + "--"; // boundary
+ // close-delimiter
+ // - RFC2046
+ // 5.1.1
+
+ if (cursor == null) {
+ cursor = new ParserCursor(0, boundaryCloseDelimiter.length());
+ }
+
+ int indexFrom = cursor.getPos();
+ int indexTo = cursor.getUpperBound();
+
+ if ((indexFrom + boundaryCloseDelimiter.length()) > indexTo) {
+ return false;
+ }
+
+ for (int i = indexFrom; i < indexTo; ++i) {
+ if (buffer.charAt(i) != boundaryCloseDelimiter.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static boolean isBoundaryDelimiter(final CharArrayBuffer buffer, ParserCursor cursor, String boundary) {
+ Args.notNull(buffer, "Buffer");
+ Args.notNull(boundary, "Boundary");
+
+ String boundaryDelimiter = "--" + boundary; // boundary delimiter -
+ // RFC2046 5.1.1
+
+ if (cursor == null) {
+ cursor = new ParserCursor(0, boundaryDelimiter.length());
+ }
+
+ int indexFrom = cursor.getPos();
+ int indexTo = cursor.getUpperBound();
+
+ if ((indexFrom + boundaryDelimiter.length()) > indexTo) {
+ return false;
+ }
+
+ for (int i = indexFrom; i < indexTo; ++i) {
+ if (buffer.charAt(i) != boundaryDelimiter.charAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static void skipPreambleAndStartBoundary(AS2SessionInputBuffer inbuffer, String boundary)
+ throws HttpException {
+
+ boolean foundStartBoundary;
+ try {
+ foundStartBoundary = false;
+ CharArrayBuffer lineBuffer = new CharArrayBuffer(1024);
+ while (inbuffer.readLine(lineBuffer) != -1) {
+ final ParserCursor cursor = new ParserCursor(0, lineBuffer.length());
+ if (isBoundaryDelimiter(lineBuffer, cursor, boundary)) {
+ foundStartBoundary = true;
+ break;
+ }
+ lineBuffer.clear();
+ }
+ } catch (Exception e) {
+ throw new HttpException("Failed to read start boundary for body part", e);
+ }
+
+ if (!foundStartBoundary) {
+ throw new HttpException("Failed to find start boundary for body part");
+ }
+
+ }
+
+ public static void skipToBoundary(AS2SessionInputBuffer inbuffer, String boundary)
+ throws HttpException {
+
+ boolean foundEndBoundary;
+ try {
+ foundEndBoundary = false;
+ CharArrayBuffer lineBuffer = new CharArrayBuffer(1024);
+ while (inbuffer.readLine(lineBuffer) != -1) {
+ final ParserCursor cursor = new ParserCursor(0, lineBuffer.length());
+ if (isBoundaryDelimiter(lineBuffer, cursor, boundary)) {
+ foundEndBoundary = true;
+ break;
+ }
+ lineBuffer.clear();
+ }
+ } catch (Exception e) {
+ throw new HttpException("Failed to read start boundary for body part", e);
+ }
+
+ if (!foundEndBoundary) {
+ throw new HttpException("Failed to find start boundary for body part");
+ }
+
+ }
+
+ public static void parseMultipartSignedEntity(HttpMessage message)
+ throws HttpException {
+ MultipartSignedEntity multipartSignedEntity = null;
+ HttpEntity entity = Args.notNull(EntityUtils.getMessageEntity(message), "message entity");
+
+ if (entity instanceof MultipartSignedEntity) {
+ return;
+ }
+
+ Args.check(entity.isStreaming(), "Entity is not streaming");
+
+ try {
+
+ // Determine and validate the Content Type
+ Header contentTypeHeader = entity.getContentType();
+ if (contentTypeHeader == null) {
+ throw new HttpException("Content-Type header is missing");
+ }
+ ContentType contentType = ContentType.parse(entity.getContentType().getValue());
+ if (!contentType.getMimeType().equals(AS2MimeType.MULTIPART_SIGNED)) {
+ throw new HttpException("Entity has invalid MIME type '" + contentType.getMimeType() + "'");
+ }
+
+ // Determine Charset
+ String charsetName = AS2Charset.US_ASCII;
+ Charset charset = contentType.getCharset();
+ if (charset != null) {
+ charsetName = charset.name();
+ }
+
+ // Determine content transfer encoding
+ String contentTransferEncoding = HttpMessageUtils.getHeaderValue(message, AS2Header.CONTENT_TRANSFER_ENCODING);
+
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE);
+ inbuffer.bind(entity.getContent());
+
+ // Get Boundary Value
+ String boundary = HttpMessageUtils.getBoundaryParameterValue(message, AS2Header.CONTENT_TYPE);
+ if (boundary == null) {
+ throw new HttpException("Failed to retrive boundary value");
+ }
+
+ multipartSignedEntity = parseMultipartSignedEntityBody(inbuffer, boundary, charsetName, contentTransferEncoding);
+
+ EntityUtils.setMessageEntity(message, multipartSignedEntity);
+
+ } catch (HttpException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HttpException("Failed to parse entity content", e);
+ }
+ }
+
+ public static void parseApplicationEDIEntity(HttpMessage message) throws HttpException {
+ ApplicationEDIEntity applicationEDIEntity = null;
+ HttpEntity entity = Args.notNull(EntityUtils.getMessageEntity(message), "message entity");
+
+ if (entity instanceof ApplicationEDIEntity) {
+ return;
+ }
+
+ Args.check(entity.isStreaming(), "Entity is not streaming");
+
+ try {
+
+ // Determine and validate the Content Type
+ Header contentTypeHeader = entity.getContentType();
+ if (contentTypeHeader == null) {
+ throw new HttpException("Content-Type header is missing");
+ }
+ ContentType contentType = ContentType.parse(entity.getContentType().getValue());
+ if (!contentType.getMimeType().startsWith(EntityParser.APPLICATION_EDI_CONTENT_TYPE_PREFIX)) {
+ throw new HttpException("Entity has invalid MIME type '" + contentType.getMimeType() + "'");
+ }
+
+ // Determine Transfer Encoding
+ Header transferEncoding = entity.getContentEncoding();
+ String contentTransferEncoding = transferEncoding == null ? null : transferEncoding.getValue();
+
+ SessionInputBufferImpl inBuffer = new SessionInputBufferImpl(new HttpTransportMetricsImpl(), 8 * 1024);
+ inBuffer.bind(entity.getContent());
+
+ // Extract content from stream
+ CharArrayBuffer lineBuffer = new CharArrayBuffer(1024);
+ while (inBuffer.readLine(lineBuffer) != -1) {
+ lineBuffer.append("\r\n"); // add line delimiter
+ }
+
+ // Build application EDI entity
+ applicationEDIEntity = EntityUtils.createEDIEntity(lineBuffer.toString(), contentType,
+ contentTransferEncoding, true);
+
+ EntityUtils.setMessageEntity(message, applicationEDIEntity);
+ } catch (HttpException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HttpException("Failed to parse entity content", e);
+ }
+ }
+
+ public static void parseMessageDispositionNotificationReportEntity(HttpMessage message)
+ throws HttpException {
+ DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity = null;
+ HttpEntity entity = Args.notNull(EntityUtils.getMessageEntity(message), "message entity");
+
+ if (entity instanceof DispositionNotificationMultipartReportEntity) {
+ return;
+ }
+
+ Args.check(entity.isStreaming(), "Entity is not streaming");
+
+ try {
+
+ // Determine and validate the Content Type
+ Header contentTypeHeader = entity.getContentType();
+ if (contentTypeHeader == null) {
+ throw new HttpException("Content-Type header is missing");
+ }
+ ContentType contentType = ContentType.parse(entity.getContentType().getValue());
+ if (!contentType.getMimeType().equals(AS2MimeType.MULTIPART_REPORT)) {
+ throw new HttpException("Entity has invalid MIME type '" + contentType.getMimeType() + "'");
+ }
+
+ // Determine Charset
+ String charsetName = AS2Charset.US_ASCII;
+ Charset charset = contentType.getCharset();
+ if (charset != null) {
+ charsetName = charset.name();
+ }
+
+ // Determine content transfer encoding
+ String contentTransferEncoding = HttpMessageUtils.getHeaderValue(message, AS2Header.CONTENT_TRANSFER_ENCODING);
+
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), 8 * 1024);
+ inbuffer.bind(entity.getContent());
+
+ // Get Boundary Value
+ String boundary = HttpMessageUtils.getBoundaryParameterValue(message, AS2Header.REPORT_TYPE);
+ if (boundary == null) {
+ throw new HttpException("Failed to retrive boundary value");
+ }
+
+ dispositionNotificationMultipartReportEntity = parseMultipartReportEntityBody(inbuffer, boundary, charsetName, contentTransferEncoding);
+
+ EntityUtils.setMessageEntity(message, dispositionNotificationMultipartReportEntity);
+
+ } catch (HttpException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HttpException("Failed to parse entity content", e);
+ }
+ }
+
+ public static void parseAS2MessageEntity(HttpMessage message) throws HttpException {
+ if (EntityUtils.hasEntity(message)) {
+ String contentTypeStr = HttpMessageUtils.getHeaderValue(message, AS2Header.CONTENT_TYPE);
+ if (contentTypeStr != null) {
+ ContentType contentType;
+ try {
+ contentType = ContentType.parse(contentTypeStr);
+ } catch (Exception e) {
+ LOG.debug("Failed to get content type of message", e);
+ return;
+ }
+ switch (contentType.getMimeType().toLowerCase()) {
+ case AS2MimeType.APPLICATION_EDIFACT:
+ case AS2MimeType.APPLICATION_EDI_X12:
+ case AS2MimeType.APPLICATION_EDI_CONSENT:
+ parseApplicationEDIEntity(message);
+ break;
+ case AS2MimeType.MULTIPART_SIGNED:
+ parseMultipartSignedEntity(message);
+ break;
+ case AS2MimeType.APPLICATION_PKCS7_MIME:
+ break;
+ case AS2MimeType.MULTIPART_REPORT:
+ parseMessageDispositionNotificationReportEntity(message);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ public static MultipartSignedEntity parseMultipartSignedEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ String charsetName,
+ String contentTransferEncoding)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+
+ if (charsetName == null) {
+ charsetName = AS2Charset.US_ASCII;
+ }
+ Charset charset = Charset.forName(charsetName);
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ MultipartSignedEntity multipartSignedEntity = new MultipartSignedEntity(boundary, false);
+
+ // Skip Preamble and Start Boundary line
+ skipPreambleAndStartBoundary(inbuffer, boundary);
+
+ //
+ // Parse Signed Entity Part
+ //
+
+ // Read Text Report Body Part Headers
+ Header[] headers = AbstractMessageParser.parseHeaders(inbuffer, -1, -1, BasicLineParser.INSTANCE,
+ new ArrayList<CharArrayBuffer>());
+
+ // Get Content-Type and Content-Transfer-Encoding
+ ContentType signedEntityContentType = null;
+ String signedEntityContentTransferEncoding = null;
+ for (Header header : headers) {
+ switch (header.getName()) {
+ case AS2Header.CONTENT_TYPE:
+ signedEntityContentType = ContentType.parse(header.getValue());
+ break;
+ case AS2Header.CONTENT_TRANSFER_ENCODING:
+ signedEntityContentTransferEncoding = header.getValue();
+ break;
+ default:
+ continue;
+ }
+ }
+ if (signedEntityContentType == null) {
+ throw new HttpException("Failed to find Content-Type header in signed entity body part");
+ }
+
+ MimeEntity signedEntity = parseEntityBody(inbuffer, boundary, signedEntityContentType, signedEntityContentTransferEncoding, headers);
+ signedEntity.removeAllHeaders();
+ signedEntity.setHeaders(headers);
+ multipartSignedEntity.addPart(signedEntity);
+
+ //
+ // End Signed Entity Part
+
+ //
+ // Parse Signature Body Part
+ //
+
+ // Read Signature Body Part Headers
+ headers = AbstractMessageParser.parseHeaders(inbuffer, -1, -1, BasicLineParser.INSTANCE,
+ new ArrayList<CharArrayBuffer>());
+
+ // Get Content-Type and Content-Transfer-Encoding
+ ContentType signatureContentType = null;
+ String signatureContentTransferEncoding = null;
+ for (Header header : headers) {
+ switch (header.getName()) {
+ case AS2Header.CONTENT_TYPE:
+ signatureContentType = ContentType.parse(header.getValue());
+ break;
+ case AS2Header.CONTENT_TRANSFER_ENCODING:
+ signatureContentTransferEncoding = header.getValue();
+ break;
+ default:
+ continue;
+ }
+ }
+ if (signatureContentType == null) {
+ throw new HttpException("Failed to find Content-Type header in signature body part");
+ }
+ if (!ContentTypeUtils.isPkcs7SignatureType(signatureContentType)) {
+ throw new HttpException(
+ "Invalid content type '" + signatureContentType.getMimeType() + "' for signature body part");
+ }
+
+ ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity = parseApplicationPkcs7SignatureEntityBody(inbuffer, boundary, signatureContentType, signatureContentTransferEncoding);
+ applicationPkcs7SignatureEntity.removeAllHeaders();
+ applicationPkcs7SignatureEntity.setHeaders(headers);
+ multipartSignedEntity.addPart(applicationPkcs7SignatureEntity);
+
+ return multipartSignedEntity;
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse text entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+ }
+
+ public static DispositionNotificationMultipartReportEntity parseMultipartReportEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ String charsetName,
+ String contentTransferEncoding)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+
+ if (charsetName == null) {
+ charsetName = AS2Charset.US_ASCII;
+ }
+ Charset charset = Charset.forName(charsetName);
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity = new DispositionNotificationMultipartReportEntity(boundary, false);
+
+ // Skip Preamble and Start Boundary line
+ skipPreambleAndStartBoundary(inbuffer, boundary);
+
+ //
+ // Parse Text Report Body Part
+ //
+
+ // Read Text Report Body Part Headers
+ Header[] headers = AbstractMessageParser.parseHeaders(inbuffer, -1, -1, BasicLineParser.INSTANCE,
+ new ArrayList<CharArrayBuffer>());
+
+ // Get Content-Type and Content-Transfer-Encoding
+ ContentType textReportContentType = null;
+ String textReportContentTransferEncoding = null;
+ for (Header header : headers) {
+ switch (header.getName()) {
+ case AS2Header.CONTENT_TYPE:
+ textReportContentType = ContentType.parse(header.getValue());
+ break;
+ case AS2Header.CONTENT_TRANSFER_ENCODING:
+ textReportContentTransferEncoding = header.getValue();
+ break;
+ default:
+ continue;
+ }
+ }
+ if (textReportContentType == null) {
+ throw new HttpException("Failed to find Content-Type header in EDI message body part");
+ }
+ if (!textReportContentType.getMimeType().equalsIgnoreCase(AS2MimeType.TEXT_PLAIN)) {
+ throw new HttpException("Invalid content type '" + textReportContentType.getMimeType()
+ + "' for first body part of disposition notification");
+ }
+
+ String textReportCharsetName = textReportContentType.getCharset() == null ? AS2Charset.US_ASCII : textReportContentType.getCharset().name();
+ TextPlainEntity textReportEntity = parseTextPlainEntityBody(inbuffer, boundary, textReportCharsetName, textReportContentTransferEncoding);
+ textReportEntity.setHeaders(headers);
+ dispositionNotificationMultipartReportEntity.addPart(textReportEntity);
+
+ //
+ // End Text Report Body Part
+
+ //
+ // Parse Disposition Notification Body Part
+ //
+
+ // Read Disposition Notification Body Part Headers
+ headers = AbstractMessageParser.parseHeaders(inbuffer, -1, -1, BasicLineParser.INSTANCE,
+ new ArrayList<CharArrayBuffer>());
+
+ // Get Content-Type and Content-Transfer-Encoding
+ ContentType dispositionNotificationContentType = null;
+ String dispositionNotificationContentTransferEncoding = null;
+ for (Header header : headers) {
+ switch (header.getName()) {
+ case AS2Header.CONTENT_TYPE:
+ dispositionNotificationContentType = ContentType.parse(header.getValue());
+ break;
+ case AS2Header.CONTENT_TRANSFER_ENCODING:
+ dispositionNotificationContentTransferEncoding = header.getValue();
+ break;
+ default:
+ continue;
+ }
+ }
+ if (dispositionNotificationContentType == null) {
+ throw new HttpException("Failed to find Content-Type header in body part");
+ }
+ if (!dispositionNotificationContentType.getMimeType()
+ .equalsIgnoreCase(AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION)) {
+ throw new HttpException("Invalid content type '" + dispositionNotificationContentType.getMimeType()
+ + "' for second body part of disposition notification");
+ }
+
+ String dispositionNotificationCharsetName = dispositionNotificationContentType.getCharset() == null ? AS2Charset.US_ASCII : dispositionNotificationContentType.getCharset().name();
+ AS2MessageDispositionNotificationEntity messageDispositionNotificationEntity = parseMessageDispositionNotificationEntityBody(
+ inbuffer, boundary, dispositionNotificationCharsetName, dispositionNotificationContentTransferEncoding);
+ messageDispositionNotificationEntity.setHeaders(headers);
+ dispositionNotificationMultipartReportEntity.addPart(messageDispositionNotificationEntity);
+
+ //
+ // End Disposition Notification Body Part
+
+ return dispositionNotificationMultipartReportEntity;
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse text entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+
+ }
+
+ public static TextPlainEntity parseTextPlainEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ String charsetName,
+ String contentTransferEncoding)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+
+ if (charsetName == null) {
+ charsetName = AS2Charset.US_ASCII;
+ }
+ Charset charset = Charset.forName(charsetName);
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ String text = parseBodyPartText(inbuffer, boundary);
+ return new TextPlainEntity(text, charsetName, contentTransferEncoding, false);
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse text entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+ }
+
+ public static AS2MessageDispositionNotificationEntity parseMessageDispositionNotificationEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ String charsetName,
+ String contentTransferEncoding)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+
+ if (charsetName == null) {
+ charsetName = AS2Charset.US_ASCII;
+ }
+ Charset charset = Charset.forName(charsetName);
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ List<CharArrayBuffer> dispositionNotificationFields = parseBodyPartFields(inbuffer, boundary,
+ BasicLineParser.INSTANCE, new ArrayList<CharArrayBuffer>());
+
+ AS2MessageDispositionNotificationEntity as2MessageDispositionNotificationEntity = DispositionNotificationContentUtils.parseDispositionNotification(dispositionNotificationFields);
+ ContentType contentType = ContentType.create(AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION, charset);
+ as2MessageDispositionNotificationEntity.setContentType(contentType);
+ return as2MessageDispositionNotificationEntity;
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse MDN entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+ }
+
+ public static MimeEntity parseEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ ContentType entityContentType,
+ String contentTransferEncoding,
+ Header[] headers)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+ Charset charset = entityContentType.getCharset();
+ if (charset == null) {
+ charset = Charset.forName(AS2Charset.US_ASCII);
+ }
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ MimeEntity entity = null;
+ switch (entityContentType.getMimeType().toLowerCase()) {
+ case AS2MimeType.APPLICATION_EDIFACT:
+ case AS2MimeType.APPLICATION_EDI_X12:
+ case AS2MimeType.APPLICATION_EDI_CONSENT:
+ entity = parseEDIEntityBody(inbuffer, boundary, entityContentType, contentTransferEncoding);
+ break;
+ case AS2MimeType.MULTIPART_SIGNED:
+ String multipartSignedBoundary = AS2HeaderUtils.getBoundaryParameterValue(headers,
+ AS2Header.CONTENT_TYPE);
+ entity = parseMultipartSignedEntityBody(inbuffer, multipartSignedBoundary, charset.name(),
+ contentTransferEncoding);
+ skipToBoundary(inbuffer, boundary);
+ break;
+ case AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION:
+ entity = parseMessageDispositionNotificationEntityBody(inbuffer, boundary, charset.name(),
+ contentTransferEncoding);
+ break;
+ case AS2MimeType.MULTIPART_REPORT:
+ String multipartReportBoundary = AS2HeaderUtils.getBoundaryParameterValue(headers,
+ AS2Header.REPORT_TYPE);
+ entity = parseMultipartReportEntityBody(inbuffer, multipartReportBoundary, charset.name(),
+ contentTransferEncoding);
+ skipToBoundary(inbuffer, boundary);
+ break;
+ case AS2MimeType.TEXT_PLAIN:
+ entity = parseTextPlainEntityBody(inbuffer, boundary, charset.name(), previousContentTransferEncoding);
+ break;
+ case AS2MimeType.APPLICATION_PKCS7_SIGNATURE:
+ entity = parseApplicationPkcs7SignatureEntityBody(inbuffer, boundary, entityContentType,
+ contentTransferEncoding);
+ break;
+ default:
+ break;
+ }
+
+ return entity;
+
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse EDI entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+
+ }
+
+ public static ApplicationEDIEntity parseEDIEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ ContentType ediMessageContentType,
+ String contentTransferEncoding)
+ throws ParseException {
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+ Charset charset = ediMessageContentType.getCharset();
+ if (charset == null) {
+ charset = Charset.forName(AS2Charset.US_ASCII);
+ }
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ String ediMessageBodyPartContent = parseBodyPartText(inbuffer, boundary);
+ ApplicationEDIEntity applicationEDIEntity = EntityUtils.createEDIEntity(ediMessageBodyPartContent,
+ ediMessageContentType, contentTransferEncoding, false);
+
+ return applicationEDIEntity;
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse EDI entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+ }
+
+ public static ApplicationPkcs7SignatureEntity parseApplicationPkcs7SignatureEntityBody(AS2SessionInputBuffer inbuffer,
+ String boundary,
+ ContentType contentType,
+ String contentTransferEncoding) throws ParseException {
+
+ CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+ String previousContentTransferEncoding = inbuffer.getTransferEncoding();
+
+ try {
+ Charset charset = contentType.getCharset();
+ if (charset == null) {
+ charset = Charset.forName(AS2Charset.US_ASCII);
+ }
+ CharsetDecoder charsetDecoder = charset.newDecoder();
+
+ inbuffer.setCharsetDecoder(charsetDecoder);
+ inbuffer.setTransferEncoding(contentTransferEncoding);
+
+ String pkcs7SignatureBodyContent = parseBodyPartText(inbuffer, boundary);
+
+ String charsetName = charset.toString();
+ ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity = new ApplicationPkcs7SignatureEntity(
+ charsetName, contentTransferEncoding, pkcs7SignatureBodyContent.getBytes(charset), false);
+ return applicationPkcs7SignatureEntity;
+ } catch (Exception e) {
+ ParseException parseException = new ParseException("failed to parse PKCS7 Signature entity");
+ parseException.initCause(e);
+ throw parseException;
+ } finally {
+ inbuffer.setCharsetDecoder(previousDecoder);
+ inbuffer.setTransferEncoding(previousContentTransferEncoding);
+ }
+ }
+
+ public static String parseBodyPartText(final AS2SessionInputBuffer inbuffer,
+ final String boundary)
+ throws IOException {
+ CharArrayBuffer buffer = new CharArrayBuffer(DEFAULT_BUFFER_SIZE);
+ CharArrayBuffer line = new CharArrayBuffer(DEFAULT_BUFFER_SIZE);
+ while (true) {
+ final int l = inbuffer.readLine(line);
+ if (l == -1) {
+ break;
+ }
+
+ if (boundary != null && isBoundaryDelimiter(line, null, boundary)) {
+ // remove last CRLF from buffer which belongs to boundary
+ int length = buffer.length();
+ buffer.setLength(length - 2);
+ break;
+ }
+
+ buffer.append(line);
+ buffer.append("\r\n");
+ line.clear();
+ }
+
+ return buffer.toString();
+ }
+
+ public static List<CharArrayBuffer> parseBodyPartFields(final AS2SessionInputBuffer inbuffer,
+ final String boundary,
+ final LineParser parser,
+ final List<CharArrayBuffer> headerLines)
+ throws IOException {
+ Args.notNull(parser, "parser");
+ Args.notNull(headerLines, "headerLines");
+ CharArrayBuffer current = null;
+ CharArrayBuffer previous = null;
+ while (true) {
+
+ if (current == null) {
+ current = new CharArrayBuffer(64);
+ }
+
+ final int l = inbuffer.readLine(current);
+ if (l == -1 || current.length() < 1) {
+ break;
+ }
+
+ if (boundary != null && isBoundaryDelimiter(current, null, boundary)) {
+ break;
+ }
+
+ // check if current line part of folded headers
+ if ((current.charAt(0) == ' ' || current.charAt(0) == '\t') && previous != null) {
+ // we have continuation of folded header : append value
+ int i = 0;
+ while (i < current.length()) {
+ final char ch = current.charAt(i);
+ if (ch != ' ' && ch != '\t') {
+ break;
+ }
+ i++;
+ }
+
+ // Just append current line to previous line
+ previous.append(' ');
+ previous.append(current, i, current.length() - i);
+
+ // leave current line buffer for reuse for next header
+ current.clear();
+ } else {
+ headerLines.add(current);
+ previous = current;
+ current = null;
+ }
+ }
+ return headerLines;
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/Importance.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/Importance.java
new file mode 100644
index 0000000..d22ac00
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/Importance.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.as2.api.entity;
+
+public enum Importance {
+ REQUIRED("required"),
+ OPTIONAL("optional");
+
+ private String importance;
+
+ private Importance(String importance) {
+ this.importance = importance;
+ }
+
+ public String getImportance() {
+ return importance;
+ }
+
+ @Override
+ public String toString() {
+ return importance;
+ }
+
+ public static Importance get(String importance) {
+ if (importance == null) {
+ return null;
+ }
+
+ switch (importance.toLowerCase()) {
+ case "required":
+ return REQUIRED;
+ case "optional":
+ return OPTIONAL;
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MimeEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MimeEntity.java
new file mode 100644
index 0000000..157d8cd
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MimeEntity.java
@@ -0,0 +1,277 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.entity.AbstractHttpEntity;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.HeaderGroup;
+import org.apache.http.util.Args;
+
+public abstract class MimeEntity extends AbstractHttpEntity {
+
+ /**
+ * An OuputStream wrapper that doesn't close its underlying output stream.
+ * <p>
+ * Instances of this stream are used by entities to attach encoding streams
+ * to underlying output stream in order to write out their encoded content
+ * and then flush and close these encoding streams without closing the
+ * underlying output stream.
+ */
+ protected static class NoCloseOutputStream extends FilterOutputStream {
+ public NoCloseOutputStream(OutputStream os) {
+ super(os);
+ }
+
+ public void close() {
+ // do nothing
+ }
+ }
+
+ protected static final long UNKNOWN_CONTENT_LENGTH = -1;
+
+ protected static final long RECALCULATE_CONTENT_LENGTH = -2;
+
+ protected boolean isMainBody;
+
+ protected Header contentTransferEncoding;
+
+ protected long contentLength = RECALCULATE_CONTENT_LENGTH;
+
+ private final HeaderGroup headergroup = new HeaderGroup();
+
+ protected MimeEntity() {
+ }
+
+ public boolean isMainBody() {
+ return isMainBody;
+ }
+
+ public void setMainBody(boolean isMainBody) {
+ this.isMainBody = isMainBody;
+ }
+
+ public String getContentTypeValue() {
+ Header contentTypeHeader = getContentType();
+ if (contentTypeHeader != null) {
+ return contentTypeHeader.getValue();
+ }
+ return null;
+ }
+
+ public void setContentType(ContentType contentType) {
+ super.setContentType(contentType == null ? null : contentType.toString());
+ }
+
+ @Override
+ public void setContentType(Header contentType) {
+ super.setContentType(contentType);
+ addHeader(contentType);
+ }
+
+ public String getContentEncodingValue() {
+ Header contentEncodingHeader = getContentEncoding();
+ if (contentEncodingHeader != null) {
+ return contentEncodingHeader.getValue();
+ }
+ return null;
+ }
+
+ @Override
+ public void setContentEncoding(Header contentEncoding) {
+ super.setContentEncoding(contentEncoding);
+ addHeader(contentEncoding);
+ }
+
+ public String getContentTransferEncodingValue() {
+ Header contentTransferEncodingHeader = getContentTransferEncoding();
+ if (contentTransferEncodingHeader != null) {
+ return contentTransferEncodingHeader.getValue();
+ }
+ return null;
+ }
+
+ /**
+ * Obtains the Content-Transfer-Encoding header.
+ * The default implementation returns the value of the
+ * {@link #contentEncoding contentTransferEncoding} attribute.
+ *
+ * @return the Content-Transfer-Encoding header, or {@code null}
+ */
+ public Header getContentTransferEncoding() {
+ return this.contentTransferEncoding;
+ }
+
+ /**
+ * Specifies the Content-Transfer-Encoding header.
+ * The default implementation sets the value of the
+ * {@link #contentTranferEncoding contentTransferEncoding} attribute.
+ *
+ * @param contentEncoding the new Content-Transfer-Encoding header, or
+ * {@code null} to unset
+ */
+ public void setContentTranserEncoding(final Header contentEncoding) {
+ this.contentTransferEncoding = contentEncoding;
+ addHeader(contentTransferEncoding);
+ }
+
+ /**
+ * Specifies the Content-Transfer-Encoding header, as a string.
+ * The default implementation calls
+ * {@link #setContentTransferEncoding(Header) setContentEncoding(Header)}.
+ *
+ * @param ceString the new Content-Transfer-Encoding header, or
+ * {@code null} to unset
+ */
+ public void setContentTransferEncoding(final String contentTranserEncoding) {
+ Header h = null;
+ if (contentTranserEncoding != null) {
+ h = new BasicHeader(AS2Header.CONTENT_TRANSFER_ENCODING, contentTranserEncoding);
+ }
+ setContentTranserEncoding(h);
+ }
+
+
+
+ public boolean containsHeader(final String name) {
+ return this.headergroup.containsHeader(name);
+ }
+
+ public Header[] getHeaders(final String name) {
+ return this.headergroup.getHeaders(name);
+ }
+
+ public Header getFirstHeader(final String name) {
+ return this.headergroup.getFirstHeader(name);
+ }
+
+ public Header getLastHeader(final String name) {
+ return this.headergroup.getLastHeader(name);
+ }
+
+ public Header[] getAllHeaders() {
+ return this.headergroup.getAllHeaders();
+ }
+
+ public void addHeader(final Header header) {
+ this.headergroup.addHeader(header);
+ }
+
+ public void addHeader(final String name, final String value) {
+ Args.notNull(name, "Header name");
+ this.headergroup.addHeader(new BasicHeader(name, value));
+ }
+
+ public void setHeader(final Header header) {
+ this.headergroup.updateHeader(header);
+ }
+
+ public void setHeader(final String name, final String value) {
+ Args.notNull(name, "Header name");
+ this.headergroup.updateHeader(new BasicHeader(name, value));
+ }
+
+ public void setHeaders(final Header[] headers) {
+ this.headergroup.setHeaders(headers);
+ }
+
+ public void removeHeader(final Header header) {
+ this.headergroup.removeHeader(header);
+ }
+
+ public void removeHeaders(final String name) {
+ if (name == null) {
+ return;
+ }
+ for (final HeaderIterator i = this.headergroup.iterator(); i.hasNext();) {
+ final Header header = i.nextHeader();
+ if (name.equalsIgnoreCase(header.getName())) {
+ i.remove();
+ }
+ }
+ }
+
+ public void removeAllHeaders() {
+ this.headergroup.clear();
+ }
+
+ public HeaderIterator headerIterator() {
+ return this.headergroup.iterator();
+ }
+
+ public HeaderIterator headerIterator(final String name) {
+ return this.headergroup.iterator(name);
+ }
+
+ @Override
+ public boolean isRepeatable() {
+ return true;
+ }
+
+ @Override
+ public boolean isStreaming() {
+ return !isRepeatable();
+ }
+
+ @Override
+ public long getContentLength() {
+ if (contentLength == RECALCULATE_CONTENT_LENGTH) {
+ // Calculate content length
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ writeTo(out);
+ contentLength = out.toByteArray().length;
+ } catch (IOException e) {
+ contentLength = MimeEntity.UNKNOWN_CONTENT_LENGTH;
+ }
+ }
+ return contentLength;
+ }
+
+ @Override
+ public InputStream getContent() throws IOException, UnsupportedOperationException {
+ final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+ writeTo(outstream);
+ outstream.flush();
+ return new ByteArrayInputStream(outstream.toByteArray());
+ }
+
+ public String getCharset() {
+ if (getContentType() == null) {
+ return AS2Charset.US_ASCII;
+ }
+ ContentType contentType = ContentType.parse(getContentType().getValue());
+ Charset charset = contentType.getCharset();
+ if (charset != null) {
+ return charset.name();
+ }
+ return AS2Charset.US_ASCII;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartMimeEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartMimeEntity.java
new file mode 100644
index 0000000..d4563aa
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartMimeEntity.java
@@ -0,0 +1,121 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.entity.ContentType;
+
+public abstract class MultipartMimeEntity extends MimeEntity {
+
+ protected String boundary;
+
+ private final List<MimeEntity> parts = new ArrayList<MimeEntity>();
+
+ public MultipartMimeEntity(ContentType contentType) {
+ this(contentType, false, null);
+ }
+
+ public MultipartMimeEntity(ContentType contentType, boolean isMainBody) {
+ this(contentType, isMainBody, null);
+ }
+
+ public MultipartMimeEntity(ContentType contentType, boolean isMainBody, String boundary) {
+ setContentType(contentType);
+ setMainBody(isMainBody);
+
+ if (boundary != null && EntityUtils.validateBoundaryValue(boundary)) {
+ this.boundary = boundary;
+ } else {
+ this.boundary = EntityUtils.createBoundaryValue();
+ }
+
+ }
+
+ protected MultipartMimeEntity() {
+ }
+
+ public void addPart(MimeEntity part) {
+ parts.add(part);
+ contentLength = RECALCULATE_CONTENT_LENGTH;
+ }
+
+ public MimeEntity getPart(int index) {
+ return parts.get(index);
+ }
+
+ public int getPartCount() {
+ return parts.size();
+ }
+
+ @Override
+ public long getContentLength() {
+ if (contentLength == RECALCULATE_CONTENT_LENGTH) {
+ // Need to (re)calculate content length
+
+ // See if their are any parts with unknown content lengths.
+ for (MimeEntity part : parts) {
+ long len = part.getContentLength();
+ if (len < 0) {
+ contentLength = MimeEntity.UNKNOWN_CONTENT_LENGTH;
+ return contentLength;
+ }
+ }
+
+ contentLength = super.getContentLength();
+ }
+ return contentLength;
+ }
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ NoCloseOutputStream ncos = new NoCloseOutputStream(outstream);
+ try (CanonicalOutputStream canonicalOutstream = new CanonicalOutputStream(ncos, getCharset())) {
+
+ // Write out mime part headers if this is not the main body of message.
+ if (!isMainBody()) {
+ HeaderIterator it = headerIterator();
+ while (it.hasNext()) {
+ Header header = it.nextHeader();
+ canonicalOutstream.writeln(header.toString());
+ }
+ canonicalOutstream.writeln(); // ensure empty line between headers and body; RFC2046 - 5.1.1
+ }
+
+ // Write out each part separated by a boundary delimiter line
+ String boundary = "--" + this.boundary;
+ // Write out parts
+ for (MimeEntity part : parts) {
+ canonicalOutstream.writeln(boundary);
+ part.writeTo(outstream);
+ canonicalOutstream.writeln(); // ensure boundary occurs at the beginning of a line; RFC2046 - 5.1.1
+ }
+
+ // Write out closing boundary delimiter line
+ canonicalOutstream.writeln(boundary + "--");
+
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartReportEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartReportEntity.java
new file mode 100644
index 0000000..20855b2
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartReportEntity.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.http.HttpException;
+import org.apache.http.entity.ContentType;
+
+public class MultipartReportEntity extends MultipartMimeEntity {
+
+ public MultipartReportEntity(String charset,
+ boolean isMainBody,
+ String boundary)
+ throws HttpException {
+
+ super(ContentType.create(AS2MimeType.MULTIPART_REPORT, charset), isMainBody, boundary);
+
+ }
+
+ protected MultipartReportEntity() {
+
+ }
+
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
new file mode 100644
index 0000000..af8e098
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
@@ -0,0 +1,109 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHeader;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.CMSProcessable;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerInformation;
+import org.bouncycastle.cms.SignerInformationStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.util.Store;
+
+public class MultipartSignedEntity extends MultipartMimeEntity {
+
+ public MultipartSignedEntity(MimeEntity data, AS2SignedDataGenerator signer, String signatureCharSet, String signatureTransferEncoding, boolean isMainBody, String boundary) throws Exception {
+ super(null, isMainBody, boundary);
+ ContentType contentType = signer.createMultipartSignedContentType(this.boundary);
+ this.contentType = new BasicHeader(AS2Header.CONTENT_TYPE, contentType.toString());
+ addPart(data);
+ ApplicationPkcs7SignatureEntity signature = new ApplicationPkcs7SignatureEntity(data, signer, signatureCharSet, signatureTransferEncoding, false);
+ addPart(signature);
+ }
+
+ protected MultipartSignedEntity(String boundary, boolean isMainBody) {
+ this.boundary = boundary;
+ this.isMainBody = isMainBody;
+ }
+
+ public boolean isValid() {
+ ApplicationEDIEntity applicationEDIEntity = getSignedDataEntity();
+ ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity = getSignatureEntity();
+
+ if (applicationEDIEntity == null || applicationPkcs7SignatureEntity == null) {
+ return false;
+ }
+
+ try {
+ ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+ applicationEDIEntity.writeTo(outstream);
+ CMSProcessable signedContent = new CMSProcessableByteArray(outstream.toByteArray());
+
+ byte[] signature = applicationPkcs7SignatureEntity.getSignature();
+ InputStream is = new ByteArrayInputStream(signature);
+
+ CMSSignedData signedData = new CMSSignedData(signedContent, is);
+
+ Store<X509CertificateHolder> store = signedData.getCertificates();
+ SignerInformationStore signers = signedData.getSignerInfos();
+
+ for (SignerInformation signer : signers.getSigners()) {
+ @SuppressWarnings("unchecked")
+ Collection<X509CertificateHolder> certCollection = store.getMatches(signer.getSID());
+
+ X509CertificateHolder certHolder = certCollection.iterator().next();
+ X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
+ if (!signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
+ return false;
+ }
+ }
+ } catch (Exception e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public ApplicationEDIEntity getSignedDataEntity() {
+ if (getPartCount() > 0 && getPart(0) instanceof ApplicationEDIEntity) {
+ return (ApplicationEDIEntity) getPart(0);
+ }
+
+ return null;
+ }
+
+ public ApplicationPkcs7SignatureEntity getSignatureEntity() {
+ if (getPartCount() > 1 && getPart(1) instanceof ApplicationPkcs7SignatureEntity) {
+ return (ApplicationPkcs7SignatureEntity) getPart(1);
+ }
+
+ return null;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/TextPlainEntity.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/TextPlainEntity.java
new file mode 100644
index 0000000..0061619
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/TextPlainEntity.java
@@ -0,0 +1,66 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.http.Header;
+import org.apache.http.HeaderIterator;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.Args;
+
+public class TextPlainEntity extends MimeEntity {
+
+ private String content;
+
+ public TextPlainEntity(String content, String charset, String contentTransferEncoding, boolean isMainBody) {
+ this.content = Args.notNull(content, "Content");
+ setContentType(ContentType.create(AS2MediaType.TEXT_PLAIN, charset));
+ setContentTransferEncoding(contentTransferEncoding);
+ setMainBody(isMainBody);
+ }
+
+ public String getText() {
+ return content;
+ }
+
+ @Override
+ public void writeTo(OutputStream outstream) throws IOException {
+ NoCloseOutputStream ncos = new NoCloseOutputStream(outstream);
+ try (CanonicalOutputStream canonicalOutstream = new CanonicalOutputStream(ncos, AS2Charset.US_ASCII)) {
+
+ // Write out mime part headers if this is not the main body of message.
+ if (!isMainBody()) {
+ HeaderIterator it = headerIterator();
+ while (it.hasNext()) {
+ Header header = it.nextHeader();
+ canonicalOutstream.writeln(header.toString());
+ }
+ canonicalOutstream.writeln(); // ensure empty line between headers and body; RFC2046 - 5.1.1
+ }
+
+ // Write out content
+ canonicalOutstream.write(content.getBytes(AS2Charset.US_ASCII), 0, content.length());
+ }
+ }
+
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpClientConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpClientConnection.java
new file mode 100644
index 0000000..86d4a9d
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpClientConnection.java
@@ -0,0 +1,64 @@
+/**
+ * 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.as2.api.io;
+
+import java.io.IOException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.config.MessageConstraints;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.impl.DefaultBHttpClientConnection;
+import org.apache.http.io.HttpMessageParserFactory;
+import org.apache.http.io.HttpMessageWriterFactory;
+
+public class AS2BHttpClientConnection extends DefaultBHttpClientConnection {
+
+ public AS2BHttpClientConnection(int buffersize,
+ CharsetDecoder chardecoder,
+ CharsetEncoder charencoder,
+ MessageConstraints constraints) {
+ super(buffersize, chardecoder, charencoder, constraints);
+ }
+
+ public AS2BHttpClientConnection(int buffersize,
+ int fragmentSizeHint,
+ CharsetDecoder chardecoder,
+ CharsetEncoder charencoder,
+ MessageConstraints constraints,
+ ContentLengthStrategy incomingContentStrategy,
+ ContentLengthStrategy outgoingContentStrategy,
+ HttpMessageWriterFactory<HttpRequest> requestWriterFactory,
+ HttpMessageParserFactory<HttpResponse> responseParserFactory) {
+ super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, incomingContentStrategy,
+ outgoingContentStrategy, requestWriterFactory, responseParserFactory);
+ }
+
+ public AS2BHttpClientConnection(int buffersize) {
+ super(buffersize);
+ }
+
+ @Override
+ public void receiveResponseEntity(HttpResponse response) throws HttpException, IOException {
+ super.receiveResponseEntity(response);
+ EntityParser.parseAS2MessageEntity(response);
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpServerConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpServerConnection.java
new file mode 100644
index 0000000..9564e9b
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2BHttpServerConnection.java
@@ -0,0 +1,66 @@
+/**
+ * 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.as2.api.io;
+
+import java.io.IOException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.config.MessageConstraints;
+import org.apache.http.entity.ContentLengthStrategy;
+import org.apache.http.impl.DefaultBHttpServerConnection;
+import org.apache.http.io.HttpMessageParserFactory;
+import org.apache.http.io.HttpMessageWriterFactory;
+
+public class AS2BHttpServerConnection extends DefaultBHttpServerConnection {
+
+ public AS2BHttpServerConnection(int buffersize) {
+ super(buffersize);
+ }
+
+ public AS2BHttpServerConnection(int buffersize,
+ CharsetDecoder chardecoder,
+ CharsetEncoder charencoder,
+ MessageConstraints constraints) {
+ super(buffersize, chardecoder, charencoder, constraints);
+ }
+
+ public AS2BHttpServerConnection(int buffersize,
+ int fragmentSizeHint,
+ CharsetDecoder chardecoder,
+ CharsetEncoder charencoder,
+ MessageConstraints constraints,
+ ContentLengthStrategy incomingContentStrategy,
+ ContentLengthStrategy outgoingContentStrategy,
+ HttpMessageParserFactory<HttpRequest> requestParserFactory,
+ HttpMessageWriterFactory<HttpResponse> responseWriterFactory) {
+ super(buffersize, fragmentSizeHint, chardecoder, charencoder, constraints, incomingContentStrategy,
+ outgoingContentStrategy, requestParserFactory, responseWriterFactory);
+ }
+
+ @Override
+ public void receiveRequestEntity(HttpEntityEnclosingRequest request) throws HttpException, IOException {
+ super.receiveRequestEntity(request);
+ EntityParser.parseAS2MessageEntity(request);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java
new file mode 100644
index 0000000..3ddc5dd
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/io/AS2SessionInputBuffer.java
@@ -0,0 +1,364 @@
+/**
+ * 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.as2.api.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.http.MessageConstraintException;
+import org.apache.http.config.MessageConstraints;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.io.BufferInfo;
+import org.apache.http.io.HttpTransportMetrics;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.Args;
+import org.apache.http.util.Asserts;
+import org.apache.http.util.ByteArrayBuffer;
+import org.apache.http.util.CharArrayBuffer;
+
+public class AS2SessionInputBuffer implements SessionInputBuffer, BufferInfo {
+
+ private final HttpTransportMetricsImpl metrics;
+ private final byte[] buffer;
+ private final ByteArrayBuffer linebuffer;
+ private final int minChunkLimit;
+ private final MessageConstraints constraints;
+
+ private CharsetDecoder decoder;
+
+ private String transferEncoding;
+
+ private InputStream instream;
+ private int bufferpos;
+ private int bufferlen;
+ private CharBuffer cbuf;
+
+ public AS2SessionInputBuffer(final HttpTransportMetricsImpl metrics,
+ final int buffersize,
+ final int minChunkLimit,
+ MessageConstraints constraints) {
+ this.metrics = Args.notNull(metrics, "metrics");
+ Args.positive(buffersize, "buffersize");
+ this.buffer = new byte[buffersize];
+ this.bufferpos = 0;
+ this.bufferlen = 0;
+ this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512;
+ this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
+ this.linebuffer = new ByteArrayBuffer(buffersize);
+ }
+
+ public AS2SessionInputBuffer(final HttpTransportMetricsImpl metrics, final int buffersize) {
+ this(metrics, buffersize, buffersize, null);
+ }
+
+ public CharsetDecoder getCharsetDecoder() {
+ return decoder;
+ }
+
+ public void setCharsetDecoder(CharsetDecoder chardecoder) {
+ this.decoder = chardecoder;
+ }
+
+ public String getTransferEncoding() {
+ return transferEncoding;
+ }
+
+ public void setTransferEncoding(String transferEncoding) {
+ this.transferEncoding = transferEncoding;
+ }
+
+ public void bind(final InputStream instream) {
+ this.instream = instream;
+ }
+
+ public boolean isBound() {
+ return this.instream != null;
+ }
+
+ @Override
+ public int length() {
+ return this.bufferlen - this.bufferpos;
+ }
+
+ @Override
+ public int capacity() {
+ return this.buffer.length;
+ }
+
+ @Override
+ public int available() {
+ return capacity() - length();
+ }
+
+ public int fillBuffer() throws IOException {
+ // compact the buffer if necessary
+ if (this.bufferpos > 0) {
+ final int len = this.bufferlen - this.bufferpos;
+ if (len > 0) {
+ System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len);
+ }
+ this.bufferpos = 0;
+ this.bufferlen = len;
+ }
+ final int l;
+ final int off = this.bufferlen;
+ final int len = this.buffer.length - off;
+ l = streamRead(this.buffer, off, len);
+ if (l == -1) {
+ return -1;
+ } else {
+ this.bufferlen = off + l;
+ this.metrics.incrementBytesTransferred(l);
+ return l;
+ }
+ }
+
+ public boolean hasBufferedData() {
+ return this.bufferpos < this.bufferlen;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ return 0;
+ }
+ if (hasBufferedData()) {
+ final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
+ System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
+ this.bufferpos += chunk;
+ return chunk;
+ }
+ // If the remaining capacity is big enough, read directly from the
+ // underlying input stream bypassing the buffer.
+ if (len > this.minChunkLimit) {
+ final int read = streamRead(b, off, len);
+ if (read > 0) {
+ this.metrics.incrementBytesTransferred(read);
+ }
+ return read;
+ } else {
+ // otherwise read to the buffer first
+ while (!hasBufferedData()) {
+ final int noRead = fillBuffer();
+ if (noRead == -1) {
+ return -1;
+ }
+ }
+ final int chunk = Math.min(len, this.bufferlen - this.bufferpos);
+ System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
+ this.bufferpos += chunk;
+ return chunk;
+ }
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ if (b == null) {
+ return 0;
+ }
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int noRead;
+ while (!hasBufferedData()) {
+ noRead = fillBuffer();
+ if (noRead == -1) {
+ return -1;
+ }
+ }
+ return this.buffer[this.bufferpos++] & 0xff;
+ }
+
+ @Override
+ public int readLine(CharArrayBuffer charbuffer) throws IOException {
+ Args.notNull(charbuffer, "Char array buffer");
+ final int maxLineLen = this.constraints.getMaxLineLength();
+ int noRead = 0;
+ boolean retry = true;
+ while (retry) {
+ // attempt to find end of line (LF)
+ int pos = -1;
+ for (int i = this.bufferpos; i < this.bufferlen; i++) {
+ if (this.buffer[i] == HTTP.LF) {
+ pos = i;
+ break;
+ }
+ }
+
+ if (maxLineLen > 0) {
+ final int currentLen = this.linebuffer.length() + (pos > 0 ? pos : this.bufferlen) - this.bufferpos;
+ if (currentLen >= maxLineLen) {
+ throw new MessageConstraintException("Maximum line length limit exceeded");
+ }
+ }
+
+ if (pos != -1) {
+ // end of line found.
+ if (this.linebuffer.isEmpty()) {
+ // the entire line is preset in the read buffer
+ return lineFromReadBuffer(charbuffer, pos);
+ }
+ retry = false;
+ addTransferDecodedBytesToLinebuffer(pos);
+ } else {
+ // end of line not found
+ if (hasBufferedData()) {
+ addTransferDecodedBytesToLinebuffer(pos);
+ }
+ noRead = fillBuffer();
+ if (noRead == -1) {
+ retry = false;
+ }
+ }
+ }
+ if (noRead == -1 && this.linebuffer.isEmpty()) {
+ // indicate the end of stream
+ return -1;
+ }
+ return lineFromLineBuffer(charbuffer);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ final CharArrayBuffer charbuffer = new CharArrayBuffer(64);
+ final int l = readLine(charbuffer);
+ if (l != -1) {
+ return charbuffer.toString();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isDataAvailable(int timeout) throws IOException {
+ return hasBufferedData();
+ }
+
+ @Override
+ public HttpTransportMetrics getMetrics() {
+ return this.metrics;
+ }
+
+ private int streamRead(final byte[] b, final int off, final int len) throws IOException {
+ Asserts.notNull(this.instream, "Input stream");
+ return this.instream.read(b, off, len);
+ }
+
+ private int lineFromLineBuffer(final CharArrayBuffer charbuffer) throws IOException {
+ // discard LF if found
+ int len = this.linebuffer.length();
+ if (len > 0) {
+ if (this.linebuffer.byteAt(len - 1) == HTTP.LF) {
+ len--;
+ }
+ // discard CR if found
+ if (len > 0) {
+ if (this.linebuffer.byteAt(len - 1) == HTTP.CR) {
+ len--;
+ }
+ }
+ }
+ if (this.decoder == null) {
+ charbuffer.append(this.linebuffer, 0, len);
+ } else {
+ final ByteBuffer bbuf = ByteBuffer.wrap(this.linebuffer.buffer(), 0, len);
+ len = appendDecoded(charbuffer, bbuf);
+ }
+ this.linebuffer.clear();
+ return len;
+ }
+
+ private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position)
+ throws IOException {
+ int pos = position;
+ final int off = this.bufferpos;
+ int len;
+ this.bufferpos = pos + 1;
+ if (pos > off && this.buffer[pos - 1] == HTTP.CR) {
+ // skip CR if found
+ pos--;
+ }
+ len = pos - off;
+ if (this.decoder == null) {
+ charbuffer.append(this.buffer, off, len);
+ } else {
+ final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len);
+ len = appendDecoded(charbuffer, bbuf);
+ }
+ return len;
+ }
+
+ private int appendDecoded(final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException {
+ if (!bbuf.hasRemaining()) {
+ return 0;
+ }
+ if (this.cbuf == null) {
+ this.cbuf = CharBuffer.allocate(1024);
+ }
+ this.decoder.reset();
+ int len = 0;
+ while (bbuf.hasRemaining()) {
+ final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true);
+ len += handleDecodingResult(result, charbuffer, bbuf);
+ }
+ final CoderResult result = this.decoder.flush(this.cbuf);
+ len += handleDecodingResult(result, charbuffer, bbuf);
+ this.cbuf.clear();
+ return len;
+ }
+
+ private int handleDecodingResult(final CoderResult result, final CharArrayBuffer charbuffer, final ByteBuffer bbuf)
+ throws IOException {
+ if (result.isError()) {
+ result.throwException();
+ }
+ this.cbuf.flip();
+ final int len = this.cbuf.remaining();
+ while (this.cbuf.hasRemaining()) {
+ charbuffer.append(this.cbuf.get());
+ }
+ this.cbuf.compact();
+ return len;
+ }
+
+ private void addTransferDecodedBytesToLinebuffer(int pos) throws IOException {
+ try {
+ int len;
+ if (pos != -1) {
+ len = pos + 1 - this.bufferpos;
+ } else {
+ len = this.bufferlen - this.bufferpos;
+ }
+ byte[] data = new byte[len];
+ System.arraycopy(this.buffer, this.bufferpos, data, 0, data.length);
+ data = EntityUtils.decode(data, transferEncoding); //
+ this.linebuffer.append(data, 0, data.length);
+ this.bufferpos = pos + 1;
+ } catch (Exception e) {
+ throw new IOException("failed to decode transfer encoding", e);
+ }
+
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestAS2.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestAS2.java
new file mode 100644
index 0000000..1cef65f
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestAS2.java
@@ -0,0 +1,84 @@
+/**
+ * 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.as2.api.protocol;
+
+import java.io.IOException;
+
+import org.apache.camel.component.as2.api.AS2ClientManager;
+import org.apache.camel.component.as2.api.AS2Constants;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.InvalidAS2NameException;
+import org.apache.camel.component.as2.api.Util;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpCoreContext;
+
+public class RequestAS2 implements HttpRequestInterceptor {
+
+ private final String as2Version;
+ private final String clientFQDN;
+
+ public RequestAS2(String as2Version, String clientFQDN) {
+ this.as2Version = as2Version;
+ this.clientFQDN = clientFQDN;
+ }
+
+ @Override
+ public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+
+ HttpCoreContext coreContext = HttpCoreContext.adapt(context);
+
+ /* MIME header */
+ request.addHeader(AS2Header.MIME_VERSION, AS2Constants.MIME_VERSION);
+
+ /* Subject header */
+ String subject = coreContext.getAttribute(AS2ClientManager.SUBJECT, String.class);
+ request.addHeader(AS2Header.SUBJECT, subject);
+
+ /* From header */
+ String from = coreContext.getAttribute(AS2ClientManager.FROM, String.class);
+ request.addHeader(AS2Header.FROM, from);
+
+ /* AS2-Version header */
+ request.addHeader(AS2Header.AS2_VERSION, as2Version);
+
+ /* AS2-From header */
+ String as2From = coreContext.getAttribute(AS2ClientManager.AS2_FROM, String.class);
+ try {
+ Util.validateAS2Name(as2From);
+ } catch (InvalidAS2NameException e) {
+ throw new HttpException("Invalid AS-From name", e);
+ }
+ request.addHeader(AS2Header.AS2_FROM, as2From);
+
+ /* AS2-To header */
+ String as2To = coreContext.getAttribute(AS2ClientManager.AS2_TO, String.class);
+ try {
+ Util.validateAS2Name(as2To);
+ } catch (InvalidAS2NameException e) {
+ throw new HttpException("Invalid AS-To name", e);
+ }
+ request.addHeader(AS2Header.AS2_TO, as2To);
+
+ /* Message-Id header*/
+ // SHOULD be set to aid in message reconciliation
+ request.addHeader(AS2Header.MESSAGE_ID, Util.createMessageId(clientFQDN));
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestMDN.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestMDN.java
new file mode 100644
index 0000000..618c401
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/RequestMDN.java
@@ -0,0 +1,62 @@
+/**
+ * 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.as2.api.protocol;
+
+import java.io.IOException;
+
+import org.apache.camel.component.as2.api.AS2ClientManager;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpCoreContext;
+import org.apache.http.util.CharArrayBuffer;
+
+public class RequestMDN implements HttpRequestInterceptor {
+
+ private static final String SIGNED_RECEIPT_PREFIX = "signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional";
+
+ @Override
+ public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
+
+ HttpCoreContext coreContext = HttpCoreContext.adapt(context);
+
+ /* Disposition-Notification-To */
+ String dispositionNotificationTo = coreContext.getAttribute(AS2ClientManager.DISPOSITION_NOTIFICATION_TO, String.class);
+ if (dispositionNotificationTo != null) {
+ request.addHeader(AS2Header.DISPOSITION_NOTIFICATION_TO, dispositionNotificationTo);
+
+ String[] micAlgorithms = coreContext.getAttribute(AS2ClientManager.SIGNED_RECEIPT_MIC_ALGORITHMS, String[].class);
+ if (micAlgorithms == null) {
+ // requesting unsigned receipt: indicate by not setting Disposition-Notification-Options header
+ } else {
+
+ CharArrayBuffer options = new CharArrayBuffer(
+ SIGNED_RECEIPT_PREFIX.length() + 5 * micAlgorithms.length);
+ options.append(SIGNED_RECEIPT_PREFIX);
+ for (String micAlgorithm : micAlgorithms) {
+ options.append("," + micAlgorithm);
+ }
+
+ request.addHeader(AS2Header.DISPOSITION_NOTIFICATION_OPTIONS, options.toString());
+ }
+ }
+
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
new file mode 100644
index 0000000..10b6056
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
@@ -0,0 +1,181 @@
+/**
+ * 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.as2.api.protocol;
+
+import java.io.IOException;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Constants;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.AS2ReportType;
+import org.apache.camel.component.as2.api.AS2ServerManager;
+import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import org.apache.camel.component.as2.api.AS2TransferEncoding;
+import org.apache.camel.component.as2.api.InvalidAS2NameException;
+import org.apache.camel.component.as2.api.Util;
+import org.apache.camel.component.as2.api.entity.AS2DispositionType;
+import org.apache.camel.component.as2.api.entity.DispositionMode;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationMultipartReportEntity;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationOptions;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationOptionsParser;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.camel.component.as2.api.util.SigningUtils;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpResponseInterceptor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpCoreContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ResponseMDN implements HttpResponseInterceptor {
+
+ public static final String BOUNDARY_PARAM_NAME = "boundary";
+
+ private static final Logger LOG = LoggerFactory.getLogger(ResponseMDN.class);
+
+ private final String as2Version;
+ private final String serverFQDN;
+ private Certificate[] signingCertificateChain;
+ private PrivateKey signingPrivateKey;
+
+ public ResponseMDN(String as2Version, String serverFQDN, Certificate[] signingCertificateChain, PrivateKey signingPrivateKey) {
+ this.as2Version = as2Version;
+ this.serverFQDN = serverFQDN;
+ this.signingCertificateChain = signingCertificateChain;
+ this.signingPrivateKey = signingPrivateKey;
+ }
+
+ @Override
+ public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
+
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode < 200 || statusCode >= 300) {
+ LOG.debug("MDN not added due to response status code: " + statusCode);
+ return;
+ }
+ LOG.debug("Adding MDN to response: " + response);
+
+ HttpCoreContext coreContext = HttpCoreContext.adapt(context);
+
+ HttpEntityEnclosingRequest request = coreContext.getAttribute(HttpCoreContext.HTTP_REQUEST, HttpEntityEnclosingRequest.class);
+ if (request == null) {
+ LOG.debug("MDN not added due to null request");
+ return;
+ }
+ LOG.debug("Processing MDN for request: " + request);
+
+ /* MIME header */
+ response.addHeader(AS2Header.MIME_VERSION, AS2Constants.MIME_VERSION);
+
+ /* AS2-Version header */
+ response.addHeader(AS2Header.AS2_VERSION, as2Version);
+
+ /* Subject header */
+ String subjectPrefix = coreContext.getAttribute(AS2ServerManager.SUBJECT, String.class);
+ String subject = HttpMessageUtils.getHeaderValue(request, AS2Header.SUBJECT);
+ if (subjectPrefix != null && subject != null) {
+ subject = subjectPrefix + subject;
+ } else if (subject != null) {
+ subject = "MDN Response To:" + subject;
+ } else {
+ subject = "Your Requested MDN Response";
+ }
+ response.addHeader(AS2Header.SUBJECT, subject);
+
+ /* From header */
+ String from = coreContext.getAttribute(AS2ServerManager.FROM, String.class);
+ response.addHeader(AS2Header.FROM, from);
+
+ /* AS2-From header */
+ String as2From = HttpMessageUtils.getHeaderValue(request, AS2Header.AS2_TO);
+ try {
+ Util.validateAS2Name(as2From);
+ } catch (InvalidAS2NameException e) {
+ throw new HttpException("Invalid AS-From name", e);
+ }
+ response.addHeader(AS2Header.AS2_FROM, as2From);
+
+ /* AS2-To header */
+ String as2To = HttpMessageUtils.getHeaderValue(request, AS2Header.AS2_FROM);
+ try {
+ Util.validateAS2Name(as2To);
+ } catch (InvalidAS2NameException e) {
+ throw new HttpException("Invalid AS-To name", e);
+ }
+ response.addHeader(AS2Header.AS2_TO, as2To);
+
+ /* Message-Id header*/
+ // SHOULD be set to aid in message reconciliation
+ response.addHeader(AS2Header.MESSAGE_ID, Util.createMessageId(serverFQDN));
+
+ if (HttpMessageUtils.getHeaderValue(request, AS2Header.DISPOSITION_NOTIFICATION_TO) != null) {
+ // Return a Message Disposition Notification Receipt in response body
+ String boundary = EntityUtils.createBoundaryValue();
+ DispositionNotificationMultipartReportEntity multipartReportEntity = new DispositionNotificationMultipartReportEntity(
+ request, response, DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
+ AS2DispositionType.PROCESSED, null, null, null, null, null, AS2Charset.US_ASCII, boundary, true);
+
+ DispositionNotificationOptions dispositionNotificationOptions = DispositionNotificationOptionsParser
+ .parseDispositionNotificationOptions(
+ HttpMessageUtils.getHeaderValue(request, AS2Header.DISPOSITION_NOTIFICATION_OPTIONS), null);
+
+ String receiptAddress = HttpMessageUtils.getHeaderValue(request, AS2Header.RECEIPT_DELIVERY_OPTION);
+ if (receiptAddress != null) {
+ // Asynchronous Delivery
+ // TODO Implement
+ } else {
+ // Synchronous Delivery
+
+ AS2SignedDataGenerator gen = null;
+ if (dispositionNotificationOptions.getSignedReceiptProtocol() != null && signingCertificateChain != null && signingPrivateKey != null) {
+ gen = SigningUtils.createSigningGenerator(signingCertificateChain, signingPrivateKey);
+ }
+
+ if (gen != null) {
+ // Create signed receipt
+ try {
+ multipartReportEntity.setMainBody(false);
+ MultipartSignedEntity multipartSignedEntity = new MultipartSignedEntity(multipartReportEntity, gen,
+ AS2Charset.US_ASCII, AS2TransferEncoding.BASE64, false, null);
+ response.setHeader(multipartSignedEntity.getContentType());
+ EntityUtils.setMessageEntity(response, multipartSignedEntity);
+ } catch (Exception e) {
+ LOG.warn("failed to sign receipt");
+ }
+ } else {
+ // Create unsigned receipt
+ Header reportTypeHeader = AS2HeaderUtils.createHeader(AS2Header.REPORT_TYPE, new String[][] {{AS2ReportType.DISPOSITION_NOTIFICATION}, {BOUNDARY_PARAM_NAME, boundary}});
+ response.addHeader(reportTypeHeader);
+ response.setHeader(AS2Header.CONTENT_TYPE, AS2MimeType.MULTIPART_REPORT);
+ EntityUtils.setMessageEntity(response, multipartReportEntity);
+ }
+ }
+
+ }
+ LOG.debug(Util.printMessage(response));
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java
new file mode 100644
index 0000000..0d6eebd
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.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.as2.api.util;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+import org.apache.camel.component.as2.api.entity.Importance;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.message.TokenParser;
+import org.apache.http.util.Args;
+import org.apache.http.util.CharArrayBuffer;
+
+public final class AS2HeaderUtils {
+
+ public static class Parameter {
+ private final String attribute;
+ private final Importance importance;
+ private final String[] values;
+
+ public Parameter(String attribute, String importance, String[] values) {
+ this.attribute = Args.notNull(attribute, "attribute");
+ this.importance = Importance.get(importance);
+ this.values = values;
+ }
+
+ public String getAttribute() {
+ return attribute;
+ }
+
+ public Importance getImportance() {
+ return importance;
+ }
+
+ public String[] getValues() {
+ return values;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(attribute);
+ if (importance != null) {
+ sb.append("=" + importance.toString());
+ }
+ if (values != null) {
+ for (String value : values) {
+ sb.append("," + value);
+ }
+ }
+ return sb.toString();
+ }
+ }
+
+ private static final char PARAM_DELIMITER = ',';
+ private static final char ELEM_DELIMITER = ';';
+ private static final char NAME_VALUE_DELIMITER = '=';
+
+ private static final TokenParser TOKEN_PARSER = TokenParser.INSTANCE;
+
+ private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET(NAME_VALUE_DELIMITER, PARAM_DELIMITER,
+ ELEM_DELIMITER);
+ private static final BitSet VALUE_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER, ELEM_DELIMITER);
+
+ private AS2HeaderUtils() {
+ }
+
+ public static Header createHeader(String headerName, String[]... elements) {
+ StringBuilder sb = new StringBuilder();
+
+ boolean firstElement = true;
+ for (String[] element: elements) {
+ if (element.length == 0) {
+ continue;
+ }
+ if (firstElement) {
+ firstElement = false;
+ } else {
+ sb.append(ELEM_DELIMITER);
+ }
+ sb.append(element[0]);
+ if (element.length > 1) {
+ sb.append(NAME_VALUE_DELIMITER + element[1]);
+ }
+ }
+ BasicHeader header = new BasicHeader(headerName, sb.toString());
+ return header;
+ }
+
+ public static Parameter parseParameter(final CharArrayBuffer buffer, final ParserCursor cursor) {
+ Args.notNull(buffer, "Char array buffer");
+ Args.notNull(cursor, "Parser cursor");
+
+ final String name = TOKEN_PARSER.parseToken(buffer, cursor, TOKEN_DELIMS);
+ if (cursor.atEnd()) {
+ return new Parameter(name, null, null);
+ }
+
+ final int delim = buffer.charAt(cursor.getPos());
+ cursor.updatePos(cursor.getPos() + 1);
+ if (delim != NAME_VALUE_DELIMITER) {
+ return new Parameter(name, null, null);
+ }
+
+ final String importance = TOKEN_PARSER.parseValue(buffer, cursor, VALUE_DELIMS);
+ if (!cursor.atEnd()) {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+
+ List<String> values = new ArrayList<String>();
+ while (!cursor.atEnd()) {
+ String value = TOKEN_PARSER.parseValue(buffer, cursor, VALUE_DELIMS);
+ values.add(value);
+ if (cursor.atEnd()) {
+ break;
+ }
+ final int delimiter = buffer.charAt(cursor.getPos());
+ if (!cursor.atEnd()) {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ if (delimiter == ELEM_DELIMITER) {
+ break;
+ }
+ }
+
+ return new Parameter(name, importance, values.toArray(new String[values.size()]));
+ }
+
+ public static String getBoundaryParameterValue(Header[] headers, String headerName) {
+ Args.notNull(headers, "headers");
+ Args.notNull(headerName, "headerName");
+ for (Header header : headers) {
+ if (header.getName().equalsIgnoreCase(headerName)) {
+ for (HeaderElement headerElement : header.getElements()) {
+ for (NameValuePair nameValuePair : headerElement.getParameters()) {
+ if (nameValuePair.getName().equalsIgnoreCase("boundary")) {
+ return nameValuePair.getValue();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/ContentTypeUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/ContentTypeUtils.java
new file mode 100644
index 0000000..2a344db
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/ContentTypeUtils.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.as2.api.util;
+
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.http.entity.ContentType;
+
+public final class ContentTypeUtils {
+
+ private ContentTypeUtils() {
+ }
+
+ public static boolean isEDIMessageContentType(ContentType ediMessageContentType) {
+ switch (ediMessageContentType.getMimeType().toLowerCase()) {
+ case AS2MediaType.APPLICATION_EDIFACT:
+ return true;
+ case AS2MediaType.APPLICATION_EDI_X12:
+ return true;
+ case AS2MediaType.APPLICATION_EDI_CONSENT:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isPkcs7SignatureType(ContentType pcks7SignatureType) {
+ switch (pcks7SignatureType.getMimeType().toLowerCase()) {
+ case AS2MimeType.APPLICATION_PKCS7_SIGNATURE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java
new file mode 100644
index 0000000..3fcbace
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtils.java
@@ -0,0 +1,300 @@
+/**
+ * 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.as2.api.util;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.component.as2.api.MDNField;
+import org.apache.camel.component.as2.api.entity.AS2DispositionModifier;
+import org.apache.camel.component.as2.api.entity.AS2DispositionType;
+import org.apache.camel.component.as2.api.entity.AS2MessageDispositionNotificationEntity;
+import org.apache.camel.component.as2.api.entity.DispositionMode;
+import org.apache.camel.component.as2.api.util.DispositionNotificationContentUtils.Field.Element;
+import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
+import org.apache.http.ParseException;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.message.TokenParser;
+import org.apache.http.util.Args;
+import org.apache.http.util.CharArrayBuffer;
+
+public final class DispositionNotificationContentUtils {
+
+ private static final String REPORTING_UA = "reporting-ua";
+ private static final String MDN_GATEWAY = "mdn-gateway";
+ private static final String FINAL_RECIPIENT = "final-recipient";
+ private static final String ORIGINAL_MESSAGE_ID = "original-message-id";
+ private static final String DISPOSITION = "disposition";
+ private static final String FAILURE = "failure";
+ private static final String ERROR = "error";
+ private static final String WARNING = "warning";
+ private static final String RECEIVED_CONTENT_MIC = "received-content-mic";
+
+ public static class Field {
+
+ public static class Element {
+
+ private final String value;
+ private final String[] parameters;
+
+ public Element(String value, String[] parameters) {
+ this.value = value;
+ this.parameters = (parameters == null) ? new String[] {} : parameters;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String[] getParameters() {
+ return parameters;
+ }
+
+ @Override
+ public String toString() {
+ return value + ((parameters.length > 0) ? ", " + String.join(",", parameters) : "");
+ }
+
+ }
+
+ private String name;
+ private Element[] elements;
+
+ public Field(String name, Element[] elements) {
+ this.name = Args.notNull(name, "name");
+ this.elements = (elements == null) ? new Element[] {} : elements;
+ }
+
+ public Field(String name, String value) {
+ this.name = Args.notNull(name, "name");
+ this.elements = new Element[] {new Element(value, null)};
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Element[] getElements() {
+ return elements;
+ }
+
+ public String getValue() {
+
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < elements.length; i++) {
+ Element element = elements[i];
+ if (i > 0) {
+ builder.append("; " + element);
+ } else {
+ builder.append(element);
+ }
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(name + ": ");
+ for (int i = 0; i < elements.length; i++) {
+ Element element = elements[i];
+ if (i > 0) {
+ sb.append("; " + element);
+ } else {
+ sb.append(element);
+ }
+ }
+ return sb.toString();
+ }
+
+ }
+
+ private static final TokenParser TOKEN_PARSER = TokenParser.INSTANCE;
+
+ private static final char PARAM_DELIMITER = ',';
+ private static final char ELEM_DELIMITER = ';';
+
+ private static final BitSet TOKEN_DELIMS = TokenParser.INIT_BITSET(PARAM_DELIMITER, ELEM_DELIMITER);
+
+ private DispositionNotificationContentUtils() {
+ }
+
+ public static AS2MessageDispositionNotificationEntity parseDispositionNotification(List<CharArrayBuffer> dispositionNotificationFields)
+ throws ParseException {
+ String reportingUA = null;
+ String mtaName = null;
+ String finalRecipient = null;
+ String originalMessageId = null;
+ DispositionMode dispositionMode = null;
+ AS2DispositionType dispositionType = null;
+ AS2DispositionModifier dispositionModifier = null;
+ List<String> failures = new ArrayList<String>();
+ List<String> errors = new ArrayList<String>();
+ List<String> warnings = new ArrayList<String>();
+ Map<String, String> extensionFields = new HashMap<String, String>();
+ ReceivedContentMic receivedContentMic = null;
+
+ for (int i = 0; i < dispositionNotificationFields.size(); i++) {
+ final CharArrayBuffer fieldLine = dispositionNotificationFields.get(i);
+ final Field field = parseDispositionField(fieldLine);
+ switch(field.getName().toLowerCase()) {
+ case REPORTING_UA: {
+ if (field.getElements().length < 1) {
+ throw new ParseException("Invalid '" + MDNField.REPORTING_UA + "' field: UA name is missing");
+ }
+ reportingUA = field.getValue();
+ break;
+ }
+ case MDN_GATEWAY: {
+ Element[] elements = field.getElements();
+ if (elements.length < 2) {
+ throw new ParseException("Invalid '" + MDNField.MDN_GATEWAY + "' field: MTA name is missing");
+ }
+ mtaName = elements[1].getValue();
+ break;
+ }
+ case FINAL_RECIPIENT: {
+ Element[] elements = field.getElements();
+ if (elements.length < 2) {
+ throw new ParseException("Invalid '" + MDNField.FINAL_RECIPIENT + "' field: recipient address is missing");
+ }
+ finalRecipient = elements[1].getValue();
+ break;
+ }
+ case ORIGINAL_MESSAGE_ID: {
+ originalMessageId = field.getValue();
+ break;
+ }
+ case DISPOSITION: {
+ Element[] elements = field.getElements();
+ if (elements.length < 2) {
+ throw new ParseException("Invalid '" + MDNField.DISPOSITION + "' field: " + field.getValue());
+ }
+ dispositionMode = DispositionMode.parseDispositionMode(elements[0].getValue());
+ if (dispositionMode == null) {
+ throw new ParseException("Invalid '" + MDNField.DISPOSITION + "' field: invalid disposition mode '" + elements[0].getValue() + "'");
+ }
+
+ String dispositionTypeString = elements[1].getValue();
+ int slash = dispositionTypeString.indexOf('/');
+ if (slash == -1) {
+ dispositionType = AS2DispositionType.parseDispositionType(dispositionTypeString);
+ } else {
+ dispositionType = AS2DispositionType.parseDispositionType(dispositionTypeString.substring(0, slash));
+ dispositionModifier = AS2DispositionModifier.parseDispositionType(dispositionTypeString.substring(slash + 1));
+ }
+ break;
+ }
+ case FAILURE:
+ failures.add(field.getValue());
+ break;
+ case ERROR:
+ errors.add(field.getValue());
+ break;
+ case WARNING:
+ warnings.add(field.getValue());
+ break;
+ case RECEIVED_CONTENT_MIC: {
+ Element[] elements = field.getElements();
+ if (elements.length < 1) {
+ throw new ParseException("Invalid '" + MDNField.RECEIVED_CONTENT_MIC + "' field: MIC is missing");
+ }
+ Element element = elements[0];
+ String[] parameters = element.getParameters();
+ if (parameters.length < 1) {
+ throw new ParseException("Invalid '" + MDNField.RECEIVED_CONTENT_MIC + "' field: digest algorithm ID is missing");
+ }
+ String digestAlgorithmId = parameters[0];
+ String encodedMessageDigest = element.getValue();
+ receivedContentMic = new ReceivedContentMic(digestAlgorithmId, encodedMessageDigest);
+ break;
+ }
+ default: // Extension Field
+ extensionFields.put(field.getName(), field.getValue());
+ }
+ }
+
+ return new AS2MessageDispositionNotificationEntity(reportingUA,
+ mtaName,
+ finalRecipient,
+ originalMessageId,
+ dispositionMode,
+ dispositionType,
+ dispositionModifier,
+ failures.toArray(new String[failures.size()]),
+ errors.toArray(new String[errors.size()]),
+ warnings.toArray(new String[warnings.size()]),
+ extensionFields,
+ receivedContentMic);
+ }
+
+ public static Field parseDispositionField(CharArrayBuffer fieldLine) {
+ final int colon = fieldLine.indexOf(':');
+ if (colon == -1) {
+ throw new ParseException("Invalid field: " + fieldLine.toString());
+ }
+ final String fieldName = fieldLine.substringTrimmed(0, colon);
+
+ ParserCursor cursor = new ParserCursor(colon + 1, fieldLine.length());
+
+ final List<Element> elements = new ArrayList<Element>();
+ while (!cursor.atEnd()) {
+ final Element element = parseDispositionFieldElement(fieldLine, cursor);
+ if (element.getValue() != null) {
+ elements.add(element);
+ }
+ }
+
+ return new Field(fieldName, elements.toArray(new Element[elements.size()]));
+ }
+
+ public static Element parseDispositionFieldElement(CharArrayBuffer fieldLine, ParserCursor cursor) {
+
+ final String value = TOKEN_PARSER.parseToken(fieldLine, cursor, TOKEN_DELIMS);
+ if (cursor.atEnd()) {
+ return new Element(value, null);
+ }
+
+ final char delim = fieldLine.charAt(cursor.getPos());
+ cursor.updatePos(cursor.getPos() + 1);
+ if (delim == ELEM_DELIMITER) {
+ return new Element(value, null);
+ }
+
+ final List<String> parameters = new ArrayList<String>();
+ while (!cursor.atEnd()) {
+ final String parameter = TOKEN_PARSER.parseToken(fieldLine, cursor, TOKEN_DELIMS);
+ parameters.add(parameter);
+ if (cursor.atEnd()) {
+ break;
+ }
+ final char ch = fieldLine.charAt(cursor.getPos());
+ if (!cursor.atEnd()) {
+ cursor.updatePos(cursor.getPos() + 1);
+ }
+ if (ch == ELEM_DELIMITER) {
+ break;
+ }
+ }
+
+ return new Element(value, parameters.toArray(new String[parameters.size()]));
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java
new file mode 100644
index 0000000..ab8c108
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/EntityUtils.java
@@ -0,0 +1,247 @@
+/**
+ * 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.as2.api.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIConsentEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIX12Entity;
+import org.apache.commons.codec.binary.Base64InputStream;
+import org.apache.commons.codec.binary.Base64OutputStream;
+import org.apache.commons.codec.net.QuotedPrintableCodec;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpMessage;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.util.Args;
+import org.bouncycastle.util.encoders.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class EntityUtils {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EntityUtils.class);
+
+ private static AtomicLong partNumber = new AtomicLong();
+
+ private EntityUtils() {
+ }
+
+ /**
+ * Generated a unique value for a Multipart boundary string.
+ * <p>
+ * The boundary string is composed of the components:
+ * "----=_Part_<global_part_number>_<newly_created_object's_hashcode>.<current_time>"
+ * <p>
+ * The generated string contains only US-ASCII characters and hence is safe
+ * for use in RFC822 headers.
+ *
+ * @return The generated boundary string.
+ */
+ public static String createBoundaryValue() {
+ // TODO: ensure boundary string is limited to 70 characters or less.
+ StringBuffer s = new StringBuffer();
+ s.append("----=_Part_").append(partNumber.incrementAndGet()).append("_").append(s.hashCode()).append(".")
+ .append(System.currentTimeMillis());
+ return s.toString();
+ }
+
+ public static boolean validateBoundaryValue(String boundaryValue) {
+ return true; // TODO: add validation logic.
+ }
+
+ public static String appendParameter(String headerString, String parameterName, String parameterValue) {
+ return headerString + "; " + parameterName + "=" + parameterValue;
+ }
+
+ public static byte[] encode(byte[] data, String encoding) throws Exception {
+ Args.notNull(data, "Data");
+
+ if (encoding == null) {
+ // Identity encoding
+ return data;
+ }
+
+ switch(encoding.toLowerCase()) {
+ case "base64":
+ return Base64.encode(data);
+ case "quoted-printable":
+ // TODO: implement QuotedPrintableOutputStream
+ return QuotedPrintableCodec.encodeQuotedPrintable(null, data);
+ case "binary":
+ case "7bit":
+ case "8bit":
+ // Identity encoding
+ return data;
+ default:
+ throw new Exception("Unknown encoding: " + encoding);
+ }
+ }
+
+ public static OutputStream encode(OutputStream os, String encoding) throws Exception {
+ Args.notNull(os, "Output Stream");
+
+ if (encoding == null) {
+ // Identity encoding
+ return os;
+ }
+ switch (encoding.toLowerCase()) {
+ case "base64":
+ return new Base64OutputStream(os, true);
+ case "quoted-printable":
+ // TODO: implement QuotedPrintableOutputStream
+ return new Base64OutputStream(os, true);
+ case "binary":
+ case "7bit":
+ case "8bit":
+ // Identity encoding
+ return os;
+ default:
+ throw new Exception("Unknown encoding: " + encoding);
+ }
+ }
+
+ public static byte[] decode(byte[] data, String encoding) throws Exception {
+ Args.notNull(data, "Input Stream");
+
+ if (encoding == null) {
+ // Identity encoding
+ return data;
+ }
+ switch (encoding.toLowerCase()) {
+ case "base64":
+ return Base64.decode(data);
+ case "quoted-printable":
+ return QuotedPrintableCodec.decodeQuotedPrintable(data);
+ case "binary":
+ case "7bit":
+ case "8bit":
+ // Identity encoding
+ return data;
+ default:
+ throw new Exception("Unknown encoding: " + encoding);
+ }
+ }
+
+ public static InputStream decode(InputStream is, String encoding) throws Exception {
+ Args.notNull(is, "Input Stream");
+
+ if (encoding == null) {
+ // Identity encoding
+ return is;
+ }
+ switch (encoding.toLowerCase()) {
+ case "base64":
+ return new Base64InputStream(is, false);
+ case "quoted-printable":
+ // TODO: implement QuotedPrintableInputStream
+ return new Base64InputStream(is, false);
+ case "binary":
+ case "7bit":
+ case "8bit":
+ // Identity encoding
+ return is;
+ default:
+ throw new Exception("Unknown encoding: " + encoding);
+ }
+ }
+
+ public static ApplicationEDIEntity createEDIEntity(String ediMessage, ContentType ediMessageContentType, String contentTransferEncoding, boolean isMainBody) throws Exception {
+ Args.notNull(ediMessage, "EDI Message");
+ Args.notNull(ediMessageContentType, "EDI Message Content Type");
+ String charset = ediMessageContentType.getCharset() == null ? AS2Charset.US_ASCII : ediMessageContentType.getCharset().toString();
+ switch(ediMessageContentType.getMimeType().toLowerCase()) {
+ case AS2MediaType.APPLICATION_EDIFACT:
+ return new ApplicationEDIFACTEntity(ediMessage, charset, contentTransferEncoding, isMainBody);
+ case AS2MediaType.APPLICATION_EDI_X12:
+ return new ApplicationEDIX12Entity(ediMessage, charset, contentTransferEncoding, isMainBody);
+ case AS2MediaType.APPLICATION_EDI_CONSENT:
+ return new ApplicationEDIConsentEntity(ediMessage, charset, contentTransferEncoding, isMainBody);
+ default:
+ throw new Exception("Invalid EDI entity mime type: " + ediMessageContentType.getMimeType());
+ }
+
+ }
+
+ public static byte[] getContent(HttpEntity entity) {
+ try {
+ final ByteArrayOutputStream outstream = new ByteArrayOutputStream();
+ entity.writeTo(outstream);
+ outstream.flush();
+ return outstream.toByteArray();
+ } catch (Exception e) {
+ LOG.debug("failed to get content", e);
+ return null;
+ }
+ }
+
+ public static boolean hasEntity(HttpMessage message) {
+ boolean hasEntity = false;
+ if (message instanceof HttpEntityEnclosingRequest) {
+ hasEntity = ((HttpEntityEnclosingRequest) message).getEntity() != null;
+ } else if (message instanceof HttpResponse) {
+ hasEntity = ((HttpResponse) message).getEntity() != null;
+ }
+ return hasEntity;
+ }
+
+ public static HttpEntity getMessageEntity(HttpMessage message) {
+ if (message instanceof HttpEntityEnclosingRequest) {
+ return ((HttpEntityEnclosingRequest) message).getEntity();
+ } else if (message instanceof HttpResponse) {
+ return ((HttpResponse) message).getEntity();
+ }
+ return null;
+ }
+
+ public static void setMessageEntity(HttpMessage message, HttpEntity entity) {
+ if (message instanceof HttpEntityEnclosingRequest) {
+ ((HttpEntityEnclosingRequest) message).setEntity(entity);
+ } else if (message instanceof HttpResponse) {
+ ((HttpResponse) message).setEntity(entity);
+ }
+ long contentLength = entity.getContentLength();
+ message.setHeader(AS2Header.CONTENT_LENGTH, Long.toString(contentLength));
+ }
+
+ public static byte[] decodeTransferEncodingOfBodyPartContent(String bodyPartContent,
+ ContentType contentType,
+ String bodyPartTransferEncoding)
+ throws Exception {
+ Args.notNull(bodyPartContent, "bodyPartContent");
+ Charset contentCharset = contentType.getCharset();
+ if (contentCharset == null) {
+ contentCharset = StandardCharsets.US_ASCII;
+ }
+ return decode(bodyPartContent.getBytes(contentCharset), bodyPartTransferEncoding);
+
+ }
+
+
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
new file mode 100644
index 0000000..0b757d7
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
@@ -0,0 +1,108 @@
+/**
+ * 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.as2.api.util;
+
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpMessage;
+import org.apache.http.NameValuePair;
+import org.apache.http.io.SessionInputBuffer;
+import org.apache.http.util.Args;
+import org.apache.http.util.CharArrayBuffer;
+
+public final class HttpMessageUtils {
+
+ private HttpMessageUtils() {
+ }
+
+ public static String getHeaderValue(HttpMessage message, String headerName) {
+ Header header = message.getFirstHeader(headerName);
+ return header == null ? null : header.getValue();
+ }
+
+ public static void setHeaderValue(HttpMessage message, String headerName, String headerValue) {
+ Args.notNull(message, "message");
+ Args.notNull(headerName, "headerName");
+ if (headerValue == null) {
+ message.removeHeaders(headerName);
+ } else {
+ message.setHeader(headerName, headerValue);
+ }
+ }
+
+ public static <T> T getEntity(HttpMessage request, Class<T> type) {
+ if (request instanceof HttpEntityEnclosingRequest) {
+ HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity();
+ if (entity != null && type.isInstance(entity)) {
+ return type.cast(entity);
+ }
+ }
+ return null;
+ }
+
+ public static String parseBodyPartContent(SessionInputBuffer inBuffer, String boundary) throws HttpException {
+ try {
+ CharArrayBuffer bodyPartContentBuffer = new CharArrayBuffer(1024);
+ CharArrayBuffer lineBuffer = new CharArrayBuffer(1024);
+ boolean foundMultipartEndBoundary = false;
+ while (inBuffer.readLine(lineBuffer) != -1) {
+ if (EntityParser.isBoundaryDelimiter(lineBuffer, null, boundary)) {
+ foundMultipartEndBoundary = true;
+ // Remove previous line ending: this is associated with
+ // boundary
+ bodyPartContentBuffer.setLength(bodyPartContentBuffer.length() - 2);
+ lineBuffer.clear();
+ break;
+ }
+ lineBuffer.append("\r\n"); // add line delimiter
+ bodyPartContentBuffer.append(lineBuffer);
+ lineBuffer.clear();
+ }
+ if (!foundMultipartEndBoundary) {
+ throw new HttpException("Failed to find end boundary delimiter for body part");
+ }
+
+ return bodyPartContentBuffer.toString();
+ } catch (HttpException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new HttpException("Failed to parse body part content", e);
+ }
+ }
+
+ public static String getBoundaryParameterValue(HttpMessage message, String headerName) {
+ Args.notNull(message, "message");
+ Args.notNull(headerName, "headerName");
+ Header header = message.getFirstHeader(headerName);
+ if (header == null) {
+ return null;
+ }
+ for (HeaderElement headerElement : header.getElements()) {
+ for (NameValuePair nameValuePair : headerElement.getParameters()) {
+ if (nameValuePair.getName().equalsIgnoreCase("boundary")) {
+ return nameValuePair.getValue();
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
new file mode 100644
index 0000000..2189951
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
@@ -0,0 +1,151 @@
+/**
+ * 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.as2.api.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MicAlgorithm;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIEntity;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationOptions;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationOptionsParser;
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.entity.ContentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public final class MicUtils {
+ private static final Logger LOG = LoggerFactory.getLogger(MicUtils.class);
+
+ private MicUtils() {
+ }
+
+ public static class ReceivedContentMic {
+ private final String digestAlgorithmId;
+ private final String encodedMessageDigest;
+
+ public ReceivedContentMic(String digestAlgorithmId, byte[] messageDigest) throws Exception {
+ this.digestAlgorithmId = digestAlgorithmId;
+ messageDigest = EntityUtils.encode(messageDigest, "base64");
+ this.encodedMessageDigest = new String(messageDigest, AS2Charset.US_ASCII);
+ }
+
+ // Used when parsing received content MIC from received string
+ protected ReceivedContentMic(String digestAlgorithmId, String encodedMessageDigest) {
+ this.digestAlgorithmId = digestAlgorithmId;
+ this.encodedMessageDigest = encodedMessageDigest;
+ }
+
+ public String getDigestAlgorithmId() {
+ return digestAlgorithmId;
+ }
+
+ public String getEncodedMessageDigest() {
+ return encodedMessageDigest;
+ }
+
+ @Override
+ public String toString() {
+ return encodedMessageDigest + "," + digestAlgorithmId;
+ }
+ }
+
+ public static byte[] createMic(byte[] content, String algorithmId) {
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance(algorithmId, "BC");
+ return messageDigest.digest(content);
+ } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
+ LOG.debug("failed to get message digets '" + algorithmId + "'");
+ return null;
+ }
+ }
+
+ public static ReceivedContentMic createReceivedContentMic(HttpEntityEnclosingRequest request) throws HttpException {
+
+ String dispositionNotificationOptionsString = HttpMessageUtils.getHeaderValue(request, AS2Header.DISPOSITION_NOTIFICATION_OPTIONS);
+ if (dispositionNotificationOptionsString == null) {
+ LOG.debug("do not create MIC: no disposition notification options in request");
+ return null;
+ }
+ DispositionNotificationOptions dispositionNotificationOptions = DispositionNotificationOptionsParser.parseDispositionNotificationOptions(dispositionNotificationOptionsString, null);
+ String micJdkAlgorithmName = getMicJdkAlgorithmName(dispositionNotificationOptions.getSignedReceiptMicalg().getValues());
+ if (micJdkAlgorithmName == null) {
+ LOG.debug("do not create MIC: no matching MIC algorithms found");
+ return null;
+ }
+
+ String contentTypeString = HttpMessageUtils.getHeaderValue(request, AS2Header.CONTENT_TYPE);
+ if (contentTypeString == null) {
+ LOG.debug("can not create MIC: content type missing from request");
+ return null;
+ }
+ ContentType contentType = ContentType.parse(contentTypeString);
+
+ HttpEntity entity = null;
+ switch (contentType.getMimeType().toLowerCase()) {
+ case AS2MimeType.APPLICATION_EDIFACT:
+ case AS2MimeType.APPLICATION_EDI_X12:
+ case AS2MimeType.APPLICATION_EDI_CONSENT: {
+ EntityParser.parseAS2MessageEntity(request);
+ entity = HttpMessageUtils.getEntity(request, ApplicationEDIEntity.class);
+ break;
+ }
+ case AS2MimeType.MULTIPART_SIGNED: {
+ EntityParser.parseAS2MessageEntity(request);
+ MultipartSignedEntity multipartSignedEntity = HttpMessageUtils.getEntity(request,
+ MultipartSignedEntity.class);
+ entity = multipartSignedEntity.getSignedDataEntity();
+ break;
+ }
+ default:
+ LOG.debug("can not create MIC: invalid content type '" + contentType.getMimeType()
+ + "' for message integrity check");
+ return null;
+ }
+
+ byte[] content = EntityUtils.getContent(entity);
+
+ String micAS2AlgorithmName = AS2MicAlgorithm.getAS2AlgorithmName(micJdkAlgorithmName);
+ byte[] mic = createMic(content, micJdkAlgorithmName);
+ try {
+ return new ReceivedContentMic(micAS2AlgorithmName, mic);
+ } catch (Exception e) {
+ throw new HttpException("failed to encode MIC", e);
+ }
+ }
+
+ public static String getMicJdkAlgorithmName(String[] micAs2AlgorithmNames) {
+ if (micAs2AlgorithmNames == null) {
+ return AS2MicAlgorithm.SHA_1.getJdkAlgorithmName();
+ }
+ for (String micAs2AlgorithmName : micAs2AlgorithmNames) {
+ String micJdkAlgorithmName = AS2MicAlgorithm.getJdkAlgorithmName(micAs2AlgorithmName);
+ if (micJdkAlgorithmName != null) {
+ return micJdkAlgorithmName;
+ }
+ }
+ return AS2MicAlgorithm.SHA_1.getJdkAlgorithmName();
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
new file mode 100644
index 0000000..7ee3bde
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
@@ -0,0 +1,92 @@
+/**
+ * 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.as2.api.util;
+
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+
+import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import org.apache.http.HttpException;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+
+public final class SigningUtils {
+
+ private SigningUtils() {
+ }
+
+ public static AS2SignedDataGenerator createSigningGenerator(Certificate[] certificateChain, PrivateKey privateKey) throws HttpException {
+
+ AS2SignedDataGenerator gen = new AS2SignedDataGenerator();
+
+ // Get first certificate in chain for signing
+ X509Certificate signingCert = (X509Certificate) certificateChain[0];
+
+ // Create capabilities vector
+ SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
+ capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
+ capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
+ capabilities.addCapability(SMIMECapability.dES_CBC);
+
+ // Create signing attributes
+ ASN1EncodableVector attributes = new ASN1EncodableVector();
+ attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(new IssuerAndSerialNumber(
+ new X500Name(signingCert.getIssuerDN().getName()), signingCert.getSerialNumber())));
+ attributes.add(new SMIMECapabilitiesAttribute(capabilities));
+
+ SignerInfoGenerator signerInfoGenerator = null;
+ for (String signingAlgorithmName : AS2SignedDataGenerator.getSupportedSignatureAlgorithmNamesForKey(privateKey)) {
+ try {
+ signerInfoGenerator = new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC")
+ .setSignedAttributeGenerator(new AttributeTable(attributes))
+ .build(signingAlgorithmName, privateKey, signingCert);
+ break;
+ } catch (Exception e) {
+ signerInfoGenerator = null;
+ continue;
+ }
+ }
+ if (signerInfoGenerator == null) {
+ throw new HttpException("Failed to create signer info");
+ }
+ gen.addSignerInfoGenerator(signerInfoGenerator);
+
+ // Create and populate certificate store.
+ try {
+ JcaCertStore certs = new JcaCertStore(Arrays.asList(certificateChain));
+ gen.addCertificates(certs);
+ } catch (CertificateEncodingException | CMSException e) {
+ throw new HttpException("Failed to add certificate chain to signature", e);
+ }
+
+ return gen;
+
+ }
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
new file mode 100644
index 0000000..34307db
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
@@ -0,0 +1,367 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.component.as2.api.entity.ApplicationEDIEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationPkcs7SignatureEntity;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpCoreContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.AttributeTable;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class AS2MessageTest {
+
+ public static final String EDI_MESSAGE = "UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'\n"
+ + "UNH+00000000000117+INVOIC:D:97B:UN'\n"
+ + "BGM+380+342459+9'\n"
+ + "DTM+3:20060515:102'\n"
+ + "RFF+ON:521052'\n"
+ + "NAD+BY+792820524::16++CUMMINS MID-RANGE ENGINE PLANT'\n"
+ + "NAD+SE+005435656::16++GENERAL WIDGET COMPANY'\n"
+ + "CUX+1:USD'\n"
+ + "LIN+1++157870:IN'\n"
+ + "IMD+F++:::WIDGET'\n"
+ + "QTY+47:1020:EA'\n"
+ + "ALI+US'\n"
+ + "MOA+203:1202.58'\n"
+ + "PRI+INV:1.179'\n"
+ + "LIN+2++157871:IN'\n"
+ + "IMD+F++:::DIFFERENT WIDGET'\n"
+ + "QTY+47:20:EA'\n"
+ + "ALI+JP'\n"
+ + "MOA+203:410'\n"
+ + "PRI+INV:20.5'\n"
+ + "UNS+S'\n"
+ + "MOA+39:2137.58'\n"
+ + "ALC+C+ABG'\n"
+ + "MOA+8:525'\n"
+ + "UNT+23+00000000000117'\n"
+ + "UNZ+1+00000000000778'";
+
+ @SuppressWarnings("unused")
+ private static final Logger LOG = LoggerFactory.getLogger(AS2MessageTest.class);
+
+ private static final String METHOD = "POST";
+ private static final String TARGET_HOST = "localhost";
+ private static final int TARGET_PORT = 8080;
+ private static final String AS2_VERSION = "1.1";
+ private static final String USER_AGENT = "Camel AS2 Endpoint";
+ private static final String REQUEST_URI = "/";
+ private static final String AS2_NAME = "878051556";
+ private static final String SUBJECT = "Test Case";
+ private static final String FROM = "mrAS@example.org";
+ private static final String CLIENT_FQDN = "client.example.org";
+ private static final String SERVER_FQDN = "server.example.org";
+ private static final String DISPOSITION_NOTIFICATION_TO = "mrAS@example.org";
+ private static final String[] SIGNED_RECEIPT_MIC_ALGORITHMS = new String[] {"sha1", "md5"};
+
+
+ private static AS2ServerConnection testServer;
+
+ private AS2SignedDataGenerator gen;
+
+ private KeyPair issueKP;
+ private X509Certificate issueCert;
+
+ private KeyPair signingKP;
+ private X509Certificate signingCert;
+ private List<X509Certificate> certList;
+
+ private void setupKeysAndCertificates() throws Exception {
+ //
+ // set up our certificates
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ String issueDN = "O=Punkhorn Software, C=US";
+ issueKP = kpg.generateKeyPair();
+ issueCert = Utils.makeCertificate(
+ issueKP, issueDN, issueKP, issueDN);
+
+ //
+ // certificate we sign against
+ //
+ String signingDN = "CN=William J. Collins, E=punkhornsw@gmail.com, O=Punkhorn Software, C=US";
+ signingKP = kpg.generateKeyPair();
+ signingCert = Utils.makeCertificate(
+ signingKP, signingDN, issueKP, issueDN);
+
+ certList = new ArrayList<X509Certificate>();
+
+ certList.add(signingCert);
+ certList.add(issueCert);
+
+ }
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+
+ //
+ // set up our certificates
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ String issueDN = "O=Punkhorn Software, C=US";
+ KeyPair issueKP = kpg.generateKeyPair();
+ X509Certificate issueCert = Utils.makeCertificate(
+ issueKP, issueDN, issueKP, issueDN);
+
+ //
+ // certificate we sign against
+ //
+ String signingDN = "CN=William J. Collins, E=punkhornsw@gmail.com, O=Punkhorn Software, C=US";
+ KeyPair signingKP = kpg.generateKeyPair();
+ X509Certificate signingCert = Utils.makeCertificate(
+ signingKP, signingDN, issueKP, issueDN);
+
+ List<X509Certificate> certList = new ArrayList<X509Certificate>();
+
+ certList.add(signingCert);
+ certList.add(issueCert);
+
+
+ testServer = new AS2ServerConnection(AS2_VERSION, "MyServer-HTTP/1.1", SERVER_FQDN, 8080, certList.toArray(new Certificate[0]), signingKP.getPrivate());
+ testServer.listen("*", new HttpRequestHandler() {
+ @Override
+ public void handle(HttpRequest request, HttpResponse response, HttpContext context)
+ throws HttpException, IOException {
+ try {
+ org.apache.camel.component.as2.api.entity.EntityParser.parseAS2MessageEntity(request);
+ context.setAttribute(SUBJECT, SUBJECT);
+ context.setAttribute(FROM, AS2_NAME);
+ } catch (Exception e) {
+ throw new HttpException("Failed to parse AS2 Message Entity", e);
+ }
+ }
+ });
+ }
+
+
+ @AfterClass
+ public static void tearDownOnce() throws Exception {
+ testServer.close();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+
+ setupKeysAndCertificates();
+
+ // Create and populate certificate store.
+ JcaCertStore certs = new JcaCertStore(certList);
+
+ // Create capabilities vector
+ SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
+ capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
+ capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
+ capabilities.addCapability(SMIMECapability.dES_CBC);
+
+ // Create signing attributes
+ ASN1EncodableVector attributes = new ASN1EncodableVector();
+ attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(new IssuerAndSerialNumber(new X500Name(signingCert.getIssuerDN().getName()), signingCert.getSerialNumber())));
+ attributes.add(new SMIMECapabilitiesAttribute(capabilities));
+
+ for (String signingAlgorithmName : AS2SignedDataGenerator
+ .getSupportedSignatureAlgorithmNamesForKey(signingKP.getPrivate())) {
+ try {
+ this.gen = new AS2SignedDataGenerator();
+ this.gen.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC")
+ .setSignedAttributeGenerator(new AttributeTable(attributes))
+ .build(signingAlgorithmName, signingKP.getPrivate(), signingCert));
+ this.gen.addCertificates(certs);
+ break;
+ } catch (Exception e) {
+ this.gen = null;
+ continue;
+ }
+ }
+
+ if (this.gen == null) {
+ throw new Exception("failed to create signing generator");
+ }
+ }
+
+ @Test
+ public void plainEDIMessageTest() throws Exception {
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.PLAIN, ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII),
+ null, null, null, DISPOSITION_NOTIFICATION_TO, SIGNED_RECEIPT_MIC_ALGORITHMS);
+
+ HttpRequest request = httpContext.getRequest();
+ assertEquals("Unexpected method value", METHOD, request.getRequestLine().getMethod());
+ assertEquals("Unexpected request URI value", REQUEST_URI, request.getRequestLine().getUri());
+ assertEquals("Unexpected HTTP version value", HttpVersion.HTTP_1_1, request.getRequestLine().getProtocolVersion());
+
+ assertEquals("Unexpected subject value", SUBJECT, request.getFirstHeader(AS2Header.SUBJECT).getValue());
+ assertEquals("Unexpected from value", FROM, request.getFirstHeader(AS2Header.FROM).getValue());
+ assertEquals("Unexpected AS2 version value", AS2_VERSION, request.getFirstHeader(AS2Header.AS2_VERSION).getValue());
+ assertEquals("Unexpected AS2 from value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_FROM).getValue());
+ assertEquals("Unexpected AS2 to value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_TO).getValue());
+ assertTrue("Unexpected message id value", request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN + ">"));
+ assertEquals("Unexpected target host value", TARGET_HOST + ":" + TARGET_PORT, request.getFirstHeader(AS2Header.TARGET_HOST).getValue());
+ assertEquals("Unexpected user agent value", USER_AGENT, request.getFirstHeader(AS2Header.USER_AGENT).getValue());
+ assertNotNull("Date value missing", request.getFirstHeader(AS2Header.DATE));
+ assertNotNull("Content length value missing", request.getFirstHeader(AS2Header.CONTENT_LENGTH));
+ assertTrue("Unexpected content type for message", request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+
+ assertTrue("Request does not contain entity", request instanceof BasicHttpEntityEnclosingRequest);
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request does not contain entity", entity);
+ assertTrue("Unexpected request entity type", entity instanceof ApplicationEDIFACTEntity);
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) entity;
+ assertTrue("Unexpected content type for entity", ediEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+ assertTrue("Entity not set as main body of request", ediEntity.isMainBody());
+ }
+
+ @Test
+ public void multipartSignedMessageTest() throws Exception {
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.SIGNED, ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII),
+ null, certList.toArray(new Certificate[0]), signingKP.getPrivate(), DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS);
+
+ HttpRequest request = httpContext.getRequest();
+ assertEquals("Unexpected method value", METHOD, request.getRequestLine().getMethod());
+ assertEquals("Unexpected request URI value", REQUEST_URI, request.getRequestLine().getUri());
+ assertEquals("Unexpected HTTP version value", HttpVersion.HTTP_1_1, request.getRequestLine().getProtocolVersion());
+
+ assertEquals("Unexpected subject value", SUBJECT, request.getFirstHeader(AS2Header.SUBJECT).getValue());
+ assertEquals("Unexpected from value", FROM, request.getFirstHeader(AS2Header.FROM).getValue());
+ assertEquals("Unexpected AS2 version value", AS2_VERSION, request.getFirstHeader(AS2Header.AS2_VERSION).getValue());
+ assertEquals("Unexpected AS2 from value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_FROM).getValue());
+ assertEquals("Unexpected AS2 to value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_TO).getValue());
+ assertTrue("Unexpected message id value", request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN + ">"));
+ assertEquals("Unexpected target host value", TARGET_HOST + ":" + TARGET_PORT, request.getFirstHeader(AS2Header.TARGET_HOST).getValue());
+ assertEquals("Unexpected user agent value", USER_AGENT, request.getFirstHeader(AS2Header.USER_AGENT).getValue());
+ assertNotNull("Date value missing", request.getFirstHeader(AS2Header.DATE));
+ assertNotNull("Content length value missing", request.getFirstHeader(AS2Header.CONTENT_LENGTH));
+ assertTrue("Unexpected content type for message", request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.MULTIPART_SIGNED));
+
+ assertTrue("Request does not contain entity", request instanceof BasicHttpEntityEnclosingRequest);
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request does not contain entity", entity);
+ assertTrue("Unexpected request entity type", entity instanceof MultipartSignedEntity);
+ MultipartSignedEntity signedEntity = (MultipartSignedEntity)entity;
+ assertTrue("Entity not set as main body of request", signedEntity.isMainBody());
+ assertTrue("Request contains invalid number of mime parts", signedEntity.getPartCount() == 2);
+
+ // Validated first mime part.
+ assertTrue("First mime part incorrect type ", signedEntity.getPart(0) instanceof ApplicationEDIFACTEntity);
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) signedEntity.getPart(0);
+ assertTrue("Unexpected content type for first mime part", ediEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+ assertFalse("First mime type set as main body of request", ediEntity.isMainBody());
+
+ // Validate second mime part.
+ assertTrue("Second mime part incorrect type ", signedEntity.getPart(1) instanceof ApplicationPkcs7SignatureEntity);
+ ApplicationPkcs7SignatureEntity signatureEntity = (ApplicationPkcs7SignatureEntity) signedEntity.getPart(1);
+ assertTrue("Unexpected content type for second mime part", signatureEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_PKCS7_SIGNATURE));
+ assertFalse("First mime type set as main body of request", signatureEntity.isMainBody());
+
+ }
+
+ @Test
+ public void signatureVerificationTest() throws Exception {
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.SIGNED, ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII),
+ null, certList.toArray(new Certificate[0]), signingKP.getPrivate(), DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS);
+
+ HttpRequest request = httpContext.getRequest();
+ assertTrue("Request does not contain entity", request instanceof BasicHttpEntityEnclosingRequest);
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request does not contain entity", entity);
+ assertTrue("Unexpected request entity type", entity instanceof MultipartSignedEntity);
+ MultipartSignedEntity signedEntity = (MultipartSignedEntity)entity;
+ ApplicationEDIEntity ediMessageEntity = signedEntity.getSignedDataEntity();
+ assertNotNull("Multipart signed entity does not contain EDI message entity", ediMessageEntity);
+ ApplicationPkcs7SignatureEntity signatureEntity = signedEntity.getSignatureEntity();
+ assertNotNull("Multipart signed entity does not contain signature entity", signatureEntity);
+
+ // Validate Signature
+ assertTrue("Signature is invalid", signedEntity.isValid());
+
+ }
+
+ @Test
+ public void mdnMessageTest() throws Exception {
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.PLAIN, ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII),
+ null, null, null, DISPOSITION_NOTIFICATION_TO, null);
+
+ @SuppressWarnings("unused")
+ HttpResponse response = httpContext.getResponse();
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/Utils.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/Utils.java
new file mode 100644
index 0000000..9ba2082
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/Utils.java
@@ -0,0 +1,83 @@
+/**
+ * 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.as2.api;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+public final class Utils {
+ //
+ // certificate serial number seed.
+ //
+ static int serialNo = 1;
+
+ private Utils() {
+ }
+
+ public static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey pub) throws IOException {
+ SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pub.getEncoded());
+
+ BcX509ExtensionUtils utils = new BcX509ExtensionUtils();
+ return utils.createAuthorityKeyIdentifier(info);
+ }
+
+ static SubjectKeyIdentifier createSubjectKeyId(PublicKey pub) throws IOException {
+ SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pub.getEncoded());
+
+ return new BcX509ExtensionUtils().createSubjectKeyIdentifier(info);
+ }
+
+ /**
+ * create a basic X509 certificate from the given keys
+ */
+ public static X509Certificate makeCertificate(KeyPair subKP, String subDN, KeyPair issKP, String issDN)
+ throws GeneralSecurityException, IOException, OperatorCreationException {
+ PublicKey subPub = subKP.getPublic();
+ PrivateKey issPriv = issKP.getPrivate();
+ PublicKey issPub = issKP.getPublic();
+
+ X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN),
+ BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()),
+ new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
+
+ v3CertGen.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyId(subPub));
+
+ v3CertGen.addExtension(Extension.authorityKeyIdentifier, false, createAuthorityKeyId(issPub));
+
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(
+ v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParserTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParserTest.java
new file mode 100644
index 0000000..d9198ec
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/DispositionNotificationOptionsParserTest.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.as2.api.entity;
+
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils.Parameter;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class DispositionNotificationOptionsParserTest {
+
+ private static final String TEST_NAME_VALUES = " signed-receipt-protocol = optional , pkcs7-signature ; signed-receipt-micalg = required , sha1 ";
+ private static final String SIGNED_RECEIPT_PROTOCOL_ATTRIBUTE = "signed-receipt-protocol";
+ private static final String SIGNED_RECEIPT_PROTOCOL_IMPORTANCE = "optional";
+ private static final String[] SIGNED_RECEIPT_PROTOCOL_VALUES = {"pkcs7-signature"};
+ private static final String SIGNED_RECEIPT_MICALG_ATTRIBUTE = "signed-receipt-micalg";
+ private static final String SIGNED_RECEIPT_MICALG_IMPORTANCE = "required";
+ private static final String[] SIGNED_RECEIPT_MICALG_VALUES = {"sha1"};
+
+ @Test
+ public void parseDispositionNotificationOptionsTest() {
+
+ DispositionNotificationOptions dispositionNotificationOptions = DispositionNotificationOptionsParser.parseDispositionNotificationOptions(TEST_NAME_VALUES, null);
+ Parameter signedReceiptProtocol = dispositionNotificationOptions.getSignedReceiptProtocol();
+ assertNotNull("signed receipt protocol not parsed", signedReceiptProtocol);
+ assertEquals("Unexpected value for signed receipt protocol attribute", SIGNED_RECEIPT_PROTOCOL_ATTRIBUTE, signedReceiptProtocol.getAttribute());
+ assertEquals("Unexpected value for signed receipt protocol importance", SIGNED_RECEIPT_PROTOCOL_IMPORTANCE, signedReceiptProtocol.getImportance().getImportance());
+ assertArrayEquals("Unexpected value for parameter importance", SIGNED_RECEIPT_PROTOCOL_VALUES, signedReceiptProtocol.getValues());
+
+ Parameter signedReceiptMicalg = dispositionNotificationOptions.getSignedReceiptMicalg();
+ assertNotNull("signed receipt micalg not parsed", signedReceiptProtocol);
+ assertEquals("Unexpected value for signed receipt micalg attribute", SIGNED_RECEIPT_MICALG_ATTRIBUTE, signedReceiptMicalg.getAttribute());
+ assertEquals("Unexpected value for signed receipt micalg importance", SIGNED_RECEIPT_MICALG_IMPORTANCE, signedReceiptMicalg.getImportance().getImportance());
+ assertArrayEquals("Unexpected value for micalg importance", SIGNED_RECEIPT_MICALG_VALUES, signedReceiptMicalg.getValues());
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java
new file mode 100644
index 0000000..6180e0e
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/entity/EntityParserTest.java
@@ -0,0 +1,239 @@
+/**
+ * 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.as2.api.entity;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.io.AS2SessionInputBuffer;
+import org.apache.camel.component.as2.api.util.EntityUtils;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.impl.EnglishReasonPhraseCatalog;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.message.BasicHttpResponse;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class EntityParserTest {
+
+ public static final String REPORT_TYPE_HEADER_VALUE =
+ "disposition-notification; boundary=\"----=_Part_56_1672293592.1028122454656\"\r\n";
+
+ public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT =
+ "\r\n"
+ + "------=_Part_56_1672293592.1028122454656\r\n"
+ + "Content-Type: text/plain\r\n"
+ + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n"
+ + "MDN for -\r\n"
+ + " Message ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + " From: \"\\\" as2Name \\\"\"\r\n"
+ + " To: \"0123456780000\""
+ + " Received on: 2002-07-31 at 09:34:14 (EDT)\r\n"
+ + " Status: processed\r\n"
+ + " Comment: This is not a guarantee that the message has\r\n"
+ + " been completely processed or &understood by the receiving\r\n"
+ + " translator\r\n" + "\r\n"
+ + "------=_Part_56_1672293592.1028122454656\r\n"
+ + "Content-Type: message/disposition-notification\r\n"
+ + "Content-Transfer-Encoding: 7bit\r\n" + "\r\n"
+ + "Reporting-UA: AS2 Server\r\n"
+ + "MDN-Gateway: dns; example.com\r\n"
+ + "Original-Recipient: rfc822; 0123456780000\r\n"
+ + "Final-Recipient: rfc822; 0123456780000\r\n"
+ + "Original-Message-ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + "Disposition: automatic-action/MDN-sent-automatically;\r\n"
+ + " processed/warning: you're awesome\r\n"
+ + "Failure: oops-a-failure\r\n" + "Error: oops-an-error\r\n"
+ + "Warning: oops-a-warning\r\n"
+ + "Received-content-MIC: 7v7F++fQaNB1sVLFtMRp+dF+eG4=, sha1\r\n"
+ + "\r\n"
+ + "------=_Part_56_1672293592.1028122454656--\r\n";
+
+ public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY = "----=_Part_56_1672293592.1028122454656";
+
+ public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME = "US-ASCII";
+
+ public static final String DISPOSITION_NOTIFICATION_REPORT_CONTENT_TRANSFER_ENCODING = "7bit";
+
+ public static final String TEXT_PLAIN_CONTENT =
+ "MDN for -\r\n"
+ + " Message ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + " From: \"\\\" as2Name \\\"\"\r\n"
+ + " To: \"0123456780000\""
+ + " Received on: 2002-07-31 at 09:34:14 (EDT)\r\n"
+ + " Status: processed\r\n"
+ + " Comment: This is not a guarantee that the message has\r\n"
+ + " been completely processed or &understood by the receiving\r\n"
+ + " translator\r\n"
+ + "\r\n"
+ + "------=_Part_56_1672293592.1028122454656--\r\n";
+
+ public static final String TEXT_PLAIN_CONTENT_BOUNDARY = "----=_Part_56_1672293592.1028122454656";
+
+ public static final String TEXT_PLAIN_CONTENT_CHARSET_NAME = "US-ASCII";
+
+ public static final String TEXT_PLAIN_CONTENT_TRANSFER_ENCODING = "7bit";
+
+ public static final String EXPECTED_TEXT_PLAIN_CONTENT =
+ "MDN for -\r\n"
+ + " Message ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + " From: \"\\\" as2Name \\\"\"\r\n"
+ + " To: \"0123456780000\""
+ + " Received on: 2002-07-31 at 09:34:14 (EDT)\r\n"
+ + " Status: processed\r\n"
+ + " Comment: This is not a guarantee that the message has\r\n"
+ + " been completely processed or &understood by the receiving\r\n"
+ + " translator\r\n";
+
+ public static final String DISPOSITION_NOTIFICATION_CONTENT =
+ "Reporting-UA: AS2 Server\r\n"
+ + "MDN-Gateway: dns; example.com\r\n"
+ + "Original-Recipient: rfc822; 0123456780000\r\n"
+ + "Final-Recipient: rfc822; 0123456780000\r\n"
+ + "Original-Message-ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + "Disposition: automatic-action/MDN-sent-automatically;\r\n"
+ + " processed/warning: you're awesome\r\n"
+ + "Failure: oops-a-failure\r\n" + "Error: oops-an-error\r\n"
+ + "Warning: oops-a-warning\r\n"
+ + "Received-content-MIC: 7v7F++fQaNB1sVLFtMRp+dF+eG4=, sha1\r\n"
+ + "\r\n"
+ + "------=_Part_56_1672293592.1028122454656--\r\n";
+
+ public static final String DISPOSITION_NOTIFICATION_CONTENT_BOUNDARY = "----=_Part_56_1672293592.1028122454656";
+
+ public static final String DISPOSITION_NOTIFICATION_CONTENT_CHARSET_NAME = "US-ASCII";
+
+ public static final String DISPOSITION_NOTIFICATION_CONTENT_TRANSFER_ENCODING = "7bit";
+
+ public static final String EXPECTED_REPORTING_UA = "AS2 Server";
+ public static final String EXPECTED_MTN_NAME = "example.com";
+ public static final String EXPECTED_ORIGINAL_RECIPIENT = "rfc822; 0123456780000";
+ public static final String EXPECTED_FINAL_RECIPIENT = "0123456780000";
+ public static final String EXPECTED_ORIGINAL_MESSAGE_ID = "<200207310834482A70BF63@\\\"~~foo~~\\\">";
+ public static final DispositionMode EXPECTED_DISPOSITION_MODE = DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY;
+ public static final String EXPECTED_DISPOSITION_MODIFIER = "warning: you're awesome";
+ public static final AS2DispositionType EXPECTED_DISPOSITION_TYPE = AS2DispositionType.PROCESSED;
+ public static final String[] EXPECTED_FAILURE = {"oops-a-failure"};
+ public static final String[] EXPECTED_ERROR = {"oops-an-error"};
+ public static final String[] EXPECTED_WARNING = {"oops-a-warning"};
+ public static final String EXPECTED_ENCODED_MESSAGE_DIGEST = "7v7F++fQaNB1sVLFtMRp+dF+eG4=";
+ public static final String EXPECTED_DIGEST_ALGORITHM_ID = "sha1";
+
+ private static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void parseMessageDispositionNotificationReportMessageTest() throws Exception {
+ HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, EnglishReasonPhraseCatalog.INSTANCE.getReason(HttpStatus.SC_OK, null));
+ HttpMessageUtils.setHeaderValue(response, AS2Header.CONTENT_TRANSFER_ENCODING, DISPOSITION_NOTIFICATION_CONTENT_TRANSFER_ENCODING);
+ HttpMessageUtils.setHeaderValue(response, AS2Header.REPORT_TYPE, REPORT_TYPE_HEADER_VALUE);
+
+ BasicHttpEntity entity = new BasicHttpEntity();
+ entity.setContentType(AS2MimeType.MULTIPART_REPORT);
+ InputStream is = new ByteArrayInputStream(DISPOSITION_NOTIFICATION_REPORT_CONTENT.getBytes(DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME));
+ entity.setContent(is);
+ EntityUtils.setMessageEntity(response, entity);
+
+ EntityParser.parseMessageDispositionNotificationReportEntity(response);
+ HttpEntity parsedEntity = EntityUtils.getMessageEntity(response);
+ assertNotNull("Unexpected Null message disposition notification report entity", parsedEntity);
+ assertTrue("Unexpected type for message disposition notification report entity", parsedEntity instanceof DispositionNotificationMultipartReportEntity);
+ }
+
+ @Test
+ public void parseMessageDispositionNotificationReportBodyTest() throws Exception {
+
+ InputStream is = new ByteArrayInputStream(DISPOSITION_NOTIFICATION_REPORT_CONTENT.getBytes(DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME));
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, null);
+ inbuffer.bind(is);
+
+ DispositionNotificationMultipartReportEntity dispositionNotificationMultipartReportEntity = EntityParser
+ .parseMultipartReportEntityBody(inbuffer, DISPOSITION_NOTIFICATION_REPORT_CONTENT_BOUNDARY,
+ DISPOSITION_NOTIFICATION_REPORT_CONTENT_CHARSET_NAME,
+ DISPOSITION_NOTIFICATION_REPORT_CONTENT_TRANSFER_ENCODING);
+
+ assertNotNull("Unexpected Null disposition notification multipart entity", dispositionNotificationMultipartReportEntity);
+ assertEquals("Unexpected number of body parts", 2, dispositionNotificationMultipartReportEntity.getPartCount());
+
+ assertTrue("Unexpected type for first body part", dispositionNotificationMultipartReportEntity.getPart(0) instanceof TextPlainEntity);
+ assertTrue("Unexpected type for second body part", dispositionNotificationMultipartReportEntity.getPart(1) instanceof AS2MessageDispositionNotificationEntity);
+ }
+
+ @Test
+ public void parseTextPlainBodyTest() throws Exception {
+
+ InputStream is = new ByteArrayInputStream(TEXT_PLAIN_CONTENT.getBytes(TEXT_PLAIN_CONTENT_CHARSET_NAME));
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, null);
+ inbuffer.bind(is);
+
+ TextPlainEntity textPlainEntity = EntityParser.parseTextPlainEntityBody(inbuffer, TEXT_PLAIN_CONTENT_BOUNDARY, TEXT_PLAIN_CONTENT_CHARSET_NAME, TEXT_PLAIN_CONTENT_TRANSFER_ENCODING);
+
+ String text = textPlainEntity.getText();
+
+ assertEquals("Unexpected text", EXPECTED_TEXT_PLAIN_CONTENT, text);
+ }
+
+ @Test
+ public void parseMessageDispositionNotificationBodyTest() throws Exception {
+
+ InputStream is = new ByteArrayInputStream(DISPOSITION_NOTIFICATION_CONTENT.getBytes(DISPOSITION_NOTIFICATION_CONTENT_CHARSET_NAME));
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE, DEFAULT_BUFFER_SIZE, null);
+ inbuffer.bind(is);
+
+ AS2MessageDispositionNotificationEntity messageDispositionNotificationEntity = EntityParser
+ .parseMessageDispositionNotificationEntityBody(inbuffer, DISPOSITION_NOTIFICATION_CONTENT_BOUNDARY,
+ DISPOSITION_NOTIFICATION_CONTENT_CHARSET_NAME,
+ DISPOSITION_NOTIFICATION_CONTENT_TRANSFER_ENCODING);
+
+ assertEquals("Unexpected Reporting UA value", EXPECTED_REPORTING_UA, messageDispositionNotificationEntity.getReportingUA());
+ assertEquals("Unexpected MTN Name", EXPECTED_MTN_NAME, messageDispositionNotificationEntity.getMtnName());
+ assertEquals("Unexpected Original Recipient", EXPECTED_ORIGINAL_RECIPIENT, messageDispositionNotificationEntity.getExtensionFields().get("Original-Recipient"));
+ assertEquals("Unexpected Final Reciptient", EXPECTED_FINAL_RECIPIENT, messageDispositionNotificationEntity.getFinalRecipient());
+ assertEquals("Unexpected Original Message ID", EXPECTED_ORIGINAL_MESSAGE_ID, messageDispositionNotificationEntity.getOriginalMessageId());
+ assertEquals("Unexpected Disposition Mode", EXPECTED_DISPOSITION_MODE, messageDispositionNotificationEntity.getDispositionMode());
+ assertNotNull("Unexpected Null Disposition Modifier", messageDispositionNotificationEntity.getDispositionModifier());
+ assertEquals("Unexpected Disposition Modifier", EXPECTED_DISPOSITION_MODIFIER, messageDispositionNotificationEntity.getDispositionModifier().getModifier());
+ assertEquals("Unexpected Disposition Type", EXPECTED_DISPOSITION_TYPE, messageDispositionNotificationEntity.getDispositionType());
+ assertArrayEquals("Unexpected Failure Array value", EXPECTED_FAILURE, messageDispositionNotificationEntity.getFailureFields());
+ assertArrayEquals("Unexpected Error Array value", EXPECTED_ERROR, messageDispositionNotificationEntity.getErrorFields());
+ assertArrayEquals("Unexpected Warning Array value", EXPECTED_WARNING, messageDispositionNotificationEntity.getWarningFields());
+ assertNotNull("Unexpected Null Received Content MIC", messageDispositionNotificationEntity.getReceivedContentMic());
+ assertEquals("Unexpected Encoded Message Digest", EXPECTED_ENCODED_MESSAGE_DIGEST, messageDispositionNotificationEntity.getReceivedContentMic().getEncodedMessageDigest());
+ assertEquals("Unexpected Digest Algorithm ID", EXPECTED_DIGEST_ALGORITHM_ID, messageDispositionNotificationEntity.getReceivedContentMic().getDigestAlgorithmId());
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java
new file mode 100644
index 0000000..7804125
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java
@@ -0,0 +1,55 @@
+/**
+ * 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.as2.api.util;
+
+import org.apache.camel.component.as2.api.util.AS2HeaderUtils.Parameter;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.util.CharArrayBuffer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class AS2HeaderUtilsTest {
+
+ private static final String TEST_NAME_VALUES = " signed-receipt-protocol = optional , pkcs7-signature ; signed-receipt-micalg = required , sha1 ";
+ private static final String SIGNED_RECEIPT_PROTOCOL_ATTRIBUTE = "signed-receipt-protocol";
+ private static final String SIGNED_RECEIPT_PROTOCOL_IMPORTANCE = "optional";
+ private static final String[] SIGNED_RECEIPT_PROTOCOL_VALUES = {"pkcs7-signature"};
+ private static final String SIGNED_RECEIPT_MICALG_ATTRIBUTE = "signed-receipt-micalg";
+ private static final String SIGNED_RECEIPT_MICALG_IMPORTANCE = "required";
+ private static final String[] SIGNED_RECEIPT_MICALG_VALUES = {"sha1"};
+
+ @Test
+ public void parseNameValuePairTest() {
+
+ final CharArrayBuffer buffer = new CharArrayBuffer(TEST_NAME_VALUES.length());
+ buffer.append(TEST_NAME_VALUES);
+ final ParserCursor cursor = new ParserCursor(0, TEST_NAME_VALUES.length());
+
+ Parameter parameter = AS2HeaderUtils.parseParameter(buffer, cursor);
+ assertEquals("Unexpected value for parameter attribute", SIGNED_RECEIPT_PROTOCOL_ATTRIBUTE, parameter.getAttribute());
+ assertEquals("Unexpected value for parameter importance", SIGNED_RECEIPT_PROTOCOL_IMPORTANCE, parameter.getImportance().getImportance());
+ assertArrayEquals("Unexpected value for parameter values", SIGNED_RECEIPT_PROTOCOL_VALUES, parameter.getValues());
+
+ parameter = AS2HeaderUtils.parseParameter(buffer, cursor);
+ assertEquals("Unexpected value for parameter attribute", SIGNED_RECEIPT_MICALG_ATTRIBUTE, parameter.getAttribute());
+ assertEquals("Unexpected value for parameter importance", SIGNED_RECEIPT_MICALG_IMPORTANCE, parameter.getImportance().getImportance());
+ assertArrayEquals("Unexpected value for parameter values", SIGNED_RECEIPT_MICALG_VALUES, parameter.getValues());
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtilsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtilsTest.java
new file mode 100644
index 0000000..2b189d5
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/DispositionNotificationContentUtilsTest.java
@@ -0,0 +1,106 @@
+/**
+ * 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.as2.api.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.camel.component.as2.api.entity.AS2DispositionType;
+import org.apache.camel.component.as2.api.entity.AS2MessageDispositionNotificationEntity;
+import org.apache.camel.component.as2.api.entity.DispositionMode;
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.camel.component.as2.api.io.AS2SessionInputBuffer;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.util.CharArrayBuffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class DispositionNotificationContentUtilsTest {
+
+ public static final String DISPOSITION_NOTIFICATION_CONTENT =
+ "Reporting-UA: AS2 Server\r\n"
+ + "MDN-Gateway: dns; example.com\r\n"
+ + "Original-Recipient: rfc822; 0123456780000\r\n"
+ + "Final-Recipient: rfc822; 0123456780000\r\n"
+ + "Original-Message-ID: <200207310834482A70BF63@\\\"~~foo~~\\\">\r\n"
+ + "Disposition: automatic-action/MDN-sent-automatically;\r\n"
+ + " processed/warning: you're awesome\r\n"
+ + "Failure: oops-a-failure\r\n"
+ + "Error: oops-an-error\r\n" + "Warning: oops-a-warning\r\n"
+ + "Received-content-MIC: 7v7F++fQaNB1sVLFtMRp+dF+eG4=, sha1\r\n"
+ + "\r\n";
+
+ public static final String EXPECTED_REPORTING_UA = "AS2 Server";
+ public static final String EXPECTED_MTN_NAME = "example.com";
+ public static final String EXPECTED_ORIGINAL_RECIPIENT = "rfc822; 0123456780000";
+ public static final String EXPECTED_FINAL_RECIPIENT = "0123456780000";
+ public static final String EXPECTED_ORIGINAL_MESSAGE_ID = "<200207310834482A70BF63@\\\"~~foo~~\\\">";
+ public static final DispositionMode EXPECTED_DISPOSITION_MODE = DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY;
+ public static final String EXPECTED_DISPOSITION_MODIFIER = "warning: you're awesome";
+ public static final AS2DispositionType EXPECTED_DISPOSITION_TYPE = AS2DispositionType.PROCESSED;
+ public static final String[] EXPECTED_FAILURE = {"oops-a-failure"};
+ public static final String[] EXPECTED_ERROR = {"oops-an-error"};
+ public static final String[] EXPECTED_WARNING = {"oops-a-warning"};
+ public static final String EXPECTED_ENCODED_MESSAGE_DIGEST = "7v7F++fQaNB1sVLFtMRp+dF+eG4=";
+ public static final String EXPECTED_DIGEST_ALGORITHM_ID = "sha1";
+
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ InputStream is = new ByteArrayInputStream(DISPOSITION_NOTIFICATION_CONTENT.getBytes());
+
+ AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), 8 * 1024);
+ inbuffer.bind(is);
+
+ List<CharArrayBuffer> dispositionNotificationFields = EntityParser.parseBodyPartFields(inbuffer, null, BasicLineParser.INSTANCE, new ArrayList<CharArrayBuffer>());
+ AS2MessageDispositionNotificationEntity messageDispositionNotificationEntity = DispositionNotificationContentUtils.parseDispositionNotification(dispositionNotificationFields);
+
+ assertEquals("Unexpected Reporting UA value", EXPECTED_REPORTING_UA, messageDispositionNotificationEntity.getReportingUA());
+ assertEquals("Unexpected MTN Name", EXPECTED_MTN_NAME, messageDispositionNotificationEntity.getMtnName());
+ assertEquals("Unexpected Original Recipient", EXPECTED_ORIGINAL_RECIPIENT, messageDispositionNotificationEntity.getExtensionFields().get("Original-Recipient"));
+ assertEquals("Unexpected Final Reciptient", EXPECTED_FINAL_RECIPIENT, messageDispositionNotificationEntity.getFinalRecipient());
+ assertEquals("Unexpected Original Message ID", EXPECTED_ORIGINAL_MESSAGE_ID, messageDispositionNotificationEntity.getOriginalMessageId());
+ assertEquals("Unexpected Disposition Mode", EXPECTED_DISPOSITION_MODE, messageDispositionNotificationEntity.getDispositionMode());
+ assertNotNull("Unexpected Null Disposition Modifier", messageDispositionNotificationEntity.getDispositionModifier());
+ assertEquals("Unexpected Disposition Modifier", EXPECTED_DISPOSITION_MODIFIER, messageDispositionNotificationEntity.getDispositionModifier().getModifier());
+ assertEquals("Unexpected Disposition Type", EXPECTED_DISPOSITION_TYPE, messageDispositionNotificationEntity.getDispositionType());
+ assertArrayEquals("Unexpected Failure Array value", EXPECTED_FAILURE, messageDispositionNotificationEntity.getFailureFields());
+ assertArrayEquals("Unexpected Error Array value", EXPECTED_ERROR, messageDispositionNotificationEntity.getErrorFields());
+ assertArrayEquals("Unexpected Warning Array value", EXPECTED_WARNING, messageDispositionNotificationEntity.getWarningFields());
+ assertNotNull("Unexpected Null Received Content MIC", messageDispositionNotificationEntity.getReceivedContentMic());
+ assertEquals("Unexpected Encoded Message Digest", EXPECTED_ENCODED_MESSAGE_DIGEST, messageDispositionNotificationEntity.getReceivedContentMic().getEncodedMessageDigest());
+ assertEquals("Unexpected Digest Algorithm ID", EXPECTED_DIGEST_ALGORITHM_ID, messageDispositionNotificationEntity.getReceivedContentMic().getDigestAlgorithmId());
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
new file mode 100644
index 0000000..6674df8
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
@@ -0,0 +1,111 @@
+/**
+ * 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.as2.api.util;
+
+import java.io.InputStream;
+import java.security.Security;
+
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.AS2TransferEncoding;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.BasicHttpEntity;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class MicUtilsTest {
+
+ public static final Logger LOG = LoggerFactory.getLogger(MicUtilsTest.class);
+
+ private static final String DISPOSITION_NOTIFICATION_OPTIONS_VALUE = " signed-receipt-protocol = optional , pkcs7-signature ; signed-receipt-micalg = required , sha1 ";
+ private static final String CONTENT_TYPE_VALUE = AS2MimeType.APPLICATION_EDIFACT;
+ private static final String EDI_MESSAGE =
+ "UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'\n"
+ + "UNH+00000000000117+INVOIC:D:97B:UN'\n"
+ + "BGM+380+342459+9'\n"
+ + "DTM+3:20060515:102'\n"
+ + "RFF+ON:521052'\n"
+ + "NAD+BY+792820524::16++CUMMINS MID-RANGE ENGINE PLANT'\n"
+ + "NAD+SE+005435656::16++GENERAL WIDGET COMPANY'\n"
+ + "CUX+1:USD'\n"
+ + "LIN+1++157870:IN'\n"
+ + "IMD+F++:::WIDGET'\n"
+ + "QTY+47:1020:EA'\n"
+ + "ALI+US'\n"
+ + "MOA+203:1202.58'\n"
+ + "PRI+INV:1.179'\n"
+ + "LIN+2++157871:IN'\n"
+ + "IMD+F++:::DIFFERENT WIDGET'\n"
+ + "QTY+47:20:EA'\n"
+ + "ALI+JP'\n"
+ + "MOA+203:410'\n"
+ + "PRI+INV:20.5'\n"
+ + "UNS+S'\n"
+ + "MOA+39:2137.58'\n"
+ + "ALC+C+ABG'\n"
+ + "MOA+8:525'\n"
+ + "UNT+23+00000000000117'\n"
+ + "UNZ+1+00000000000778'";
+
+ private static final String EXPECTED_MESSAGE_DIGEST_ALGORITHM = "sha1";
+ private static final String EXPECTED_ENCODED_MESSAGE_DIGEST = "PaQyByOuBL7XiYyts4Sdmvl1WME=";
+
+ @Before
+ public void setUp() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void createReceivedContentMicTest() throws Exception {
+
+ HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", "/", HttpVersion.HTTP_1_1);
+ request.addHeader(AS2Header.DISPOSITION_NOTIFICATION_OPTIONS, DISPOSITION_NOTIFICATION_OPTIONS_VALUE);
+ request.addHeader(AS2Header.CONTENT_TYPE, CONTENT_TYPE_VALUE);
+
+ ApplicationEDIFACTEntity edifactEntity = new ApplicationEDIFACTEntity(EDI_MESSAGE, AS2Charset.US_ASCII, AS2TransferEncoding.NONE, true);
+ InputStream is = edifactEntity.getContent();
+ BasicHttpEntity basicEntity = new BasicHttpEntity();
+ basicEntity.setContent(is);
+ basicEntity.setContentType(CONTENT_TYPE_VALUE);
+ request.setEntity(basicEntity);
+
+ ReceivedContentMic receivedContentMic = MicUtils.createReceivedContentMic(request);
+ assertNotNull("Failed to create Received Content MIC");
+ LOG.debug("Digest Algorithm: " + receivedContentMic.getDigestAlgorithmId());
+ assertEquals("Unexpected digest algorithm value", EXPECTED_MESSAGE_DIGEST_ALGORITHM, receivedContentMic.getDigestAlgorithmId());
+ LOG.debug("Encoded Message Digest: " + receivedContentMic.getEncodedMessageDigest());
+ assertEquals("Unexpected encoded message digest value", EXPECTED_ENCODED_MESSAGE_DIGEST, receivedContentMic.getEncodedMessageDigest());
+
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-api/src/test/resources/log4j2.properties b/components/camel-as2/camel-as2-api/src/test/resources/log4j2.properties
new file mode 100644
index 0000000..d9f0508
--- /dev/null
+++ b/components/camel-as2/camel-as2-api/src/test/resources/log4j2.properties
@@ -0,0 +1,23 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n
+rootLogger.level = INFO
+rootLogger.appenderRef.out.ref = out
diff --git a/components/camel-as2/camel-as2-component/pom.xml b/components/camel-as2/camel-as2-component/pom.xml
new file mode 100644
index 0000000..5d6fec0
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/pom.xml
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-as2-parent</artifactId>
+ <version>2.22.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-as2</artifactId>
+ <packaging>jar</packaging>
+ <name>Camel :: AS2 :: Component</name>
+ <description>Camel AS2 component</description>
+
+ <properties>
+ <schemeName>as2</schemeName>
+ <componentName>AS2</componentName>
+ <componentPackage>org.apache.camel.component.as2</componentPackage>
+ <outPackage>org.apache.camel.component.as2.internal</outPackage>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-as2-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <!-- Camel annotations in provided scope to avoid compile errors in IDEs -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>spi-annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Component API javadoc in provided scope to read API signatures -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-as2-api</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- logging -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <defaultGoal>install</defaultGoal>
+
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ </plugin>
+
+ <!-- to generate the MANIFEST-FILE of the bundle -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>3.2.0</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Name>Camel Component for ${componentName}</Bundle-Name>
+ <Bundle-SymbolicName>org.apache.camel.camel-as2</Bundle-SymbolicName>
+ <Export-Service>org.apache.camel.spi.ComponentResolver;component=${schemeName}</Export-Service>
+ <Export-Package>${componentPackage};version=${project.version}</Export-Package>
+ <Import-Package>
+ ${componentPackage}.api;version=${project.version},
+ ${componentPackage};version=${project.version},
+ org.apache.camel.*;version=2.18.3
+ </Import-Package>
+ <Private-Package>${outPackage}</Private-Package>
+ <Implementation-Title>Apache Camel</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Karaf-Info>Camel;${project.artifactId}=${project.version}</Karaf-Info>
+ <_versionpolicy>[$(version;==;$(@)),$(version;+;$(@)))</_versionpolicy>
+ <_failok>false</_failok>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <!-- generate Component source and test source -->
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-api-component-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>generate-test-component-classes</id>
+ <goals>
+ <goal>fromApis</goal>
+ </goals>
+ <configuration>
+ <apis>
+ <api>
+ <apiName>send</apiName>
+ <proxyClass>org.apache.camel.component.as2.api.AS2ClientManager</proxyClass>
+ <fromJavadoc>
+ <excludeMethods>createSigningGenerator</excludeMethods>
+ </fromJavadoc>
+ </api>
+ <api>
+ <apiName>listen</apiName>
+ <proxyClass>org.apache.camel.component.as2.api.AS2ServerManager</proxyClass>
+ <fromJavadoc>
+ <excludeMethods>stopListening|handleMDNResponse</excludeMethods>
+ </fromJavadoc>
+ <excludeConfigNames>handler</excludeConfigNames>
+ </api>
+ </apis>
+ <!-- Specify global values for all APIs here, these are overridden at API level
+ <substitutions/>
+ <excludeConfigNames/>
+ <excludeConfigTypes/>
+ <extraOptions/>
+ <fromJavadoc/>
+ <aliases/>
+ <nullableOptions/>
+ -->
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- generate components meta-data and validate component includes documentation etc -->
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-package-maven-plugin</artifactId>
+ <version>${project.version}</version>
+ <executions>
+ <execution>
+ <id>prepare</id>
+ <goals>
+ <goal>prepare-components</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ </execution>
+ <execution>
+ <id>validate</id>
+ <goals>
+ <goal>validate-components</goal>
+ </goals>
+ <phase>prepare-package</phase>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- add generated source and test source to build -->
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>add-generated-sources</id>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-sources/camel-component</source>
+ </sources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>add-generated-test-sources</id>
+ <goals>
+ <goal>add-test-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>${project.build.directory}/generated-test-sources/camel-component</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-api-component-maven-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <scheme>${schemeName}</scheme>
+ <componentName>${componentName}</componentName>
+ <componentPackage>${componentPackage}</componentPackage>
+ <outPackage>${outPackage}</outPackage>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+
+ </build>
+
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.camel</groupId>
+ <artifactId>camel-api-component-maven-plugin</artifactId>
+ <version>${project.version}</version>
+ <configuration>
+ <scheme>${schemeName}</scheme>
+ <componentName>${componentName}</componentName>
+ <componentPackage>${componentPackage}</componentPackage>
+ <outPackage>${outPackage}</outPackage>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+
+</project>
diff --git a/components/camel-as2/camel-as2-component/src/main/java/META-INF/MANIFEST.MF b/components/camel-as2/camel-as2-component/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..5e94951
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path:
+
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Component.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Component.java
new file mode 100644
index 0000000..2bfd0de
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Component.java
@@ -0,0 +1,59 @@
+/**
+ * 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.as2;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Endpoint;
+import org.apache.camel.component.as2.internal.AS2ApiCollection;
+import org.apache.camel.component.as2.internal.AS2ApiName;
+import org.apache.camel.util.component.AbstractApiComponent;
+
+/**
+ * Represents the component that manages {@link AS2Endpoint}.
+ */
+public class AS2Component extends AbstractApiComponent<AS2ApiName, AS2Configuration, AS2ApiCollection> {
+
+ public AS2Component() {
+ super(AS2Endpoint.class, AS2ApiName.class, AS2ApiCollection.getCollection());
+ }
+
+ public AS2Component(CamelContext context) {
+ super(context, AS2Endpoint.class, AS2ApiName.class, AS2ApiCollection.getCollection());
+ }
+
+ @Override
+ protected AS2ApiName getApiName(String apiNameStr) throws IllegalArgumentException {
+ return AS2ApiName.fromValue(apiNameStr);
+ }
+
+ @Override
+ protected Endpoint createEndpoint(String uri, String methodName, AS2ApiName apiName,
+ AS2Configuration endpointConfiguration) {
+ AS2Endpoint endpoint = new AS2Endpoint(uri, this, apiName, methodName, endpointConfiguration);
+ endpoint.setName(methodName);
+ return endpoint;
+ }
+
+ /**
+ * To use the shared configuration
+ */
+ @Override
+ public void setConfiguration(AS2Configuration configuration) {
+ super.setConfiguration(configuration);
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
new file mode 100644
index 0000000..6ad35c1
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
@@ -0,0 +1,432 @@
+/**
+ * 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.as2;
+
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.internal.AS2ApiName;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriParam;
+import org.apache.camel.spi.UriParams;
+import org.apache.camel.spi.UriPath;
+import org.apache.http.entity.ContentType;
+
+/**
+ * Component configuration for AS2 component.
+ */
+@UriParams
+public class AS2Configuration {
+
+ @UriPath
+ @Metadata(required = "true")
+ private AS2ApiName apiName;
+
+ @UriPath
+ @Metadata(required = "true")
+ private String methodName;
+
+ @UriPath
+ private String as2Version = "1.1";
+
+ @UriParam
+ private String userAgent = "Camel AS2 Client Endpoint";
+
+ @UriParam
+ private String server = "Camel AS2 Server Endpoint";
+
+ @UriParam
+ private String serverFqdn = "camel.apache.org";
+
+ @UriParam
+ private String targetHostname;
+
+ @UriParam
+ private Integer targetPortNumber;
+
+ @UriParam
+ private String clientFqdn = "camel.apache.org";
+
+ @UriParam
+ private Integer serverPortNumber;
+
+ @UriParam
+ private String requestUri = "/";
+
+ @UriParam
+ private ContentType ediMessageType;
+
+ @UriParam
+ private String ediMessageTransferEncoding;
+
+ @UriParam
+ private AS2MessageStructure as2MessageStructure;
+
+ @UriParam
+ private String subject;
+
+ @UriParam
+ private String from;
+
+ @UriParam
+ private String as2From;
+
+ @UriParam
+ private String as2To;
+
+ @UriParam
+ private String signingAlgorithmName;
+
+ @UriParam
+ private Certificate[] signingCertificateChain;
+
+ @UriParam
+ private PrivateKey signingPrivateKey;
+
+ @UriParam
+ private String dispositionNotificationTo;
+
+ @UriParam
+ private String[] signedReceiptMicAlgorithms;
+
+ /**
+ * What kind of operation to perform
+ *
+ * @return the API Name
+ */
+ public AS2ApiName getApiName() {
+ return apiName;
+ }
+
+ /**
+ * What kind of operation to perform
+ *
+ * @param apiName -
+ * the API Name to set
+ */
+ public void setApiName(AS2ApiName apiName) {
+ this.apiName = apiName;
+ }
+
+ /**
+ * What sub operation to use for the selected operation
+ *
+ * @return The methodName
+ */
+ public String getMethodName() {
+ return methodName;
+ }
+
+ /**
+ * What sub operation to use for the selected operation
+ *
+ * @param methodName -
+ * the methodName to set
+ */
+ public void setMethodName(String methodName) {
+ this.methodName = methodName;
+ }
+
+ /**
+ * The version of the AS2 protocol.
+ *
+ * @return The version of the AS2 protocol.
+ */
+ public String getAs2Version() {
+ return as2Version;
+ }
+
+ /**
+ * The version of the AS2 protocol.
+ *
+ * @param as2Version - the version of the AS2 protocol.
+ */
+ public void setAs2Version(String as2Version) {
+ if (!as2Version.equals("1.0") && !as2Version.equals("1.1")) {
+ throw new IllegalArgumentException(String.format("Value '%s' of configuration parameter 'as2Version' must be either '1.0' or '1.1'", as2Version));
+ }
+ this.as2Version = as2Version;
+ }
+
+ /**
+ * The value included in the <code>User-Agent</code>
+ * message header identifying the AS2 user agent.
+ *
+ * @return AS2 user agent identification string.
+ */
+ public String getUserAgent() {
+ return userAgent;
+ }
+
+ /**
+ * The value included in the <code>User-Agent</code>
+ * message header identifying the AS2 user agent.
+ *
+ * @param userAgent - AS2 user agent identification string.
+ */
+ public void setUserAgent(String userAgent) {
+ this.userAgent = userAgent;
+ }
+
+ /**
+ * The value included in the <code>Server</code>
+ * message header identifying the AS2 Server.
+ *
+ * @return AS2 server identification string.
+ */
+ public String getServer() {
+ return server;
+ }
+
+ /**
+ * The value included in the <code>Server</code>
+ * message header identifying the AS2 Server.
+ *
+ * @param server - AS2 server identification string.
+ */
+ public void setServer(String server) {
+ this.server = server;
+ }
+
+ /**
+ * The Server Fully Qualified Domain Name (FQDN).
+ *
+ * <p> Used in message ids sent by endpoint.
+ *
+ * @return The FQDN of client.
+ */
+ public String getServerFqdn() {
+ return serverFqdn;
+ }
+
+ /**
+ * The Server Fully Qualified Domain Name (FQDN).
+ *
+ * <p> Used in message ids sent by endpoint.
+ *
+ * @param clientFqdn - the FQDN of client.
+ */
+ public void setServerFqdn(String serverFqdn) {
+ if (clientFqdn == null) {
+ throw new RuntimeCamelException("Parameter 'serverFqdn' can not be null");
+ }
+ this.serverFqdn = serverFqdn;
+ }
+
+ /**
+ * The host name (IP or DNS) of target host.
+ *
+ * @return The target host name (IP or DNS name).
+ */
+ public String getTargetHostname() {
+ return targetHostname;
+ }
+
+ /**
+ * The host name (IP or DNS name) of target host.
+ *
+ * @param targetHostname - the target host name (IP or DNS name).
+ */
+ public void setTargetHostname(String targetHostname) {
+ this.targetHostname = targetHostname;
+ }
+
+ /**
+ * The port number of target host.
+ *
+ * @return The target port number. -1 indicates the scheme default port.
+ */
+ public int getTargetPortNumber() {
+ return targetPortNumber;
+ }
+
+ /**
+ * The port number of target host.
+ *
+ * @param targetPortNumber - the target port number. -1 indicates the scheme default port.
+ */
+ public void setTargetPortNumber(String targetPortNumber) {
+ try {
+ this.targetPortNumber = Integer.valueOf(targetPortNumber);
+ } catch (NumberFormatException e) {
+ throw new RuntimeCamelException(String.format("Invalid target port number: %s", targetPortNumber));
+ }
+ }
+
+ /**
+ * The Client Fully Qualified Domain Name (FQDN).
+ *
+ * <p> Used in message ids sent by endpoint.
+ *
+ * @return The FQDN of client.
+ */
+ public String getClientFqdn() {
+ return clientFqdn;
+ }
+
+ /**
+ * The Client Fully Qualified Domain Name (FQDN).
+ *
+ * <p> Used in message ids sent by endpoint.
+ *
+ * @param clientFqdn - the FQDN of client.
+ */
+ public void setClientFqdn(String clientFqdn) {
+ if (clientFqdn == null) {
+ throw new RuntimeCamelException("Parameter 'clientFqdn' can not be null");
+ }
+ this.clientFqdn = clientFqdn;
+ }
+
+ /**
+ * The port number of server.
+ *
+ * @return The server port number.
+ */
+ public Integer getServerPortNumber() {
+ return serverPortNumber;
+ }
+
+ /**
+ * The port number of server.
+ *
+ * @param serverPortNumber - the server port number.
+ */
+ public void setServerPortNumber(String serverPortNumber) {
+ try {
+ this.serverPortNumber = Integer.valueOf(serverPortNumber);
+ } catch (NumberFormatException e) {
+ throw new RuntimeCamelException(String.format("Invalid target port number: %s", targetPortNumber));
+ }
+ }
+
+ public String getRequestUri() {
+ return requestUri;
+ }
+
+ public void setRequestUri(String requestUri) {
+ this.requestUri = requestUri;
+ }
+
+ public ContentType getEdiMessageType() {
+ return ediMessageType;
+ }
+
+ public void setEdiMessageType(ContentType ediMessageType) {
+ this.ediMessageType = ediMessageType;
+ }
+
+ public String getEdiMessageTransferEncoding() {
+ return ediMessageTransferEncoding;
+ }
+
+ public void setEdiMessageTransferEncoding(String ediMessageTransferEncoding) {
+ this.ediMessageTransferEncoding = ediMessageTransferEncoding;
+ }
+
+ public AS2MessageStructure getAs2MessageStructure() {
+ return as2MessageStructure;
+ }
+
+ public void setAs2MessageStructure(AS2MessageStructure as2MessageStructure) {
+ this.as2MessageStructure = as2MessageStructure;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public void setSubject(String subject) {
+ this.subject = subject;
+ }
+
+ public String getFrom() {
+ return from;
+ }
+
+ public void setFrom(String from) {
+ this.from = from;
+ }
+
+ public String getAs2From() {
+ return as2From;
+ }
+
+ public void setAs2From(String as2From) {
+ this.as2From = as2From;
+ }
+
+ public String getAs2To() {
+ return as2To;
+ }
+
+ public void setAs2To(String as2To) {
+ this.as2To = as2To;
+ }
+
+ public String getSigningAlgorithmName() {
+ return signingAlgorithmName;
+ }
+
+ public void setSigningAlgorithmName(String signingAlgorithmName) {
+ this.signingAlgorithmName = signingAlgorithmName;
+ }
+
+ public Certificate[] getSigningCertificateChain() {
+ return signingCertificateChain;
+ }
+
+ public void setSigningCertificateChain(Certificate[] signingCertificateChain) {
+ this.signingCertificateChain = signingCertificateChain;
+ }
+
+ public PrivateKey getSigningPrivateKey() {
+ return signingPrivateKey;
+ }
+
+ public void setSigningPrivateKey(PrivateKey signingPrivateKey) {
+ this.signingPrivateKey = signingPrivateKey;
+ }
+
+ public void setTargetPortNumber(Integer targetPortNumber) {
+ this.targetPortNumber = targetPortNumber;
+ }
+
+ public void setServerPortNumber(Integer serverPortNumber) {
+ this.serverPortNumber = serverPortNumber;
+ }
+
+ public String getDispositionNotificationTo() {
+ return dispositionNotificationTo;
+ }
+
+ public void setDispositionNotificationTo(String dispositionNotificationTo) {
+ this.dispositionNotificationTo = dispositionNotificationTo;
+ }
+
+ public String[] getSignedReceiptMicAlgorithms() {
+ return signedReceiptMicAlgorithms;
+ }
+
+ public void setSignedReceiptMicAlgorithms(String[] signedReceiptMicAlgorithms) {
+ this.signedReceiptMicAlgorithms = signedReceiptMicAlgorithms;
+ }
+
+
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
new file mode 100644
index 0000000..2616b59
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
@@ -0,0 +1,116 @@
+/**
+ * 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.as2;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.Processor;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+import org.apache.camel.component.as2.api.AS2ServerManager;
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.camel.component.as2.internal.AS2ApiName;
+import org.apache.camel.util.component.AbstractApiConsumer;
+import org.apache.camel.util.component.ApiConsumerHelper;
+import org.apache.camel.util.component.ApiMethod;
+import org.apache.camel.util.component.ApiMethodHelper;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+
+/**
+ * The AS2 consumer.
+ */
+public class AS2Consumer extends AbstractApiConsumer<AS2ApiName, AS2Configuration> implements HttpRequestHandler {
+
+ private static final String HANDLER_PROPERTY = "handler";
+ private static final String REQUEST_URI_PROPERTY = "requestUri";
+
+ private AS2ServerConnection as2ServerConnection;
+
+ private AS2ServerManager apiProxy;
+
+ private final ApiMethod apiMethod;
+
+ private final Map<String, Object> properties;
+
+ public AS2Consumer(AS2Endpoint endpoint, Processor processor) {
+ super(endpoint, processor);
+
+ apiMethod = ApiConsumerHelper.findMethod(endpoint, this);
+
+ // Add listener property to register this consumer as listener for
+ // events.
+ properties = new HashMap<String, Object>();
+ properties.putAll(endpoint.getEndpointProperties());
+ properties.put(HANDLER_PROPERTY, this);
+
+ as2ServerConnection = endpoint.getAS2ServerConnection();
+
+ apiProxy = new AS2ServerManager(as2ServerConnection);
+ }
+
+ @Override
+ public void interceptPropertyNames(Set<String> propertyNames) {
+ propertyNames.add(HANDLER_PROPERTY);
+ }
+
+ @Override
+ protected int poll() throws Exception {
+ return 0;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+
+ // invoke the API method to start listening
+ ApiMethodHelper.invokeMethod(apiProxy, apiMethod, properties);
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ String requestUri = (String) properties.get(REQUEST_URI_PROPERTY);
+ apiProxy.stopListening(requestUri);
+
+ super.doStop();
+ }
+
+ @Override
+ public void handle(HttpRequest request, HttpResponse response, HttpContext context)
+ throws HttpException, IOException {
+ try {
+ if (request instanceof HttpEntityEnclosingRequest) {
+ EntityParser.parseAS2MessageEntity(request);
+ // TODO derive last to parameters from configuration.
+ apiProxy.handleMDNResponse((HttpEntityEnclosingRequest)request, response, context, "MDN Response", "Camel AS2 Server Endpoint");
+ }
+ // Convert HTTP context to exchange and process
+ log.debug("Processed {} event for {}", ApiConsumerHelper.getResultsProcessed(this, context, false),
+ as2ServerConnection);
+ } catch (Exception e) {
+ log.info("Received exception consuming AS2 message: ", e);
+ }
+
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java
new file mode 100644
index 0000000..6002fd4
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java
@@ -0,0 +1,162 @@
+/**
+ * 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.as2;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.Map;
+
+import org.apache.camel.Consumer;
+import org.apache.camel.Processor;
+import org.apache.camel.Producer;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.component.as2.api.AS2ClientConnection;
+import org.apache.camel.component.as2.api.AS2ClientManager;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+import org.apache.camel.component.as2.api.AS2ServerManager;
+import org.apache.camel.component.as2.internal.AS2ApiCollection;
+import org.apache.camel.component.as2.internal.AS2ApiName;
+import org.apache.camel.component.as2.internal.AS2ConnectionHelper;
+import org.apache.camel.component.as2.internal.AS2Constants;
+import org.apache.camel.component.as2.internal.AS2PropertiesHelper;
+import org.apache.camel.spi.Metadata;
+import org.apache.camel.spi.UriEndpoint;
+import org.apache.camel.spi.UriPath;
+import org.apache.camel.util.component.AbstractApiEndpoint;
+import org.apache.camel.util.component.ApiMethod;
+import org.apache.camel.util.component.ApiMethodPropertiesHelper;
+
+/**
+ * Represents a AS2 endpoint.
+ */
+@UriEndpoint(scheme = "as2", title = "AS2", syntax = "as2:name", consumerClass = AS2Consumer.class, label = "AS2")
+public class AS2Endpoint extends AbstractApiEndpoint<AS2ApiName, AS2Configuration> {
+
+ @UriPath @Metadata(required = "true")
+ private String name;
+
+ private Object apiProxy;
+
+ private AS2ClientConnection as2ClientConnection;
+
+ private AS2ServerConnection as2ServerConnection;
+
+ public AS2Endpoint(String uri, AS2Component component,
+ AS2ApiName apiName, String methodName, AS2Configuration endpointConfiguration) {
+ super(uri, component, apiName, methodName, AS2ApiCollection.getCollection().getHelper(apiName), endpointConfiguration);
+
+ }
+
+ public AS2ClientConnection getAS2ClientConnection() {
+ return as2ClientConnection;
+ }
+
+ public AS2ServerConnection getAS2ServerConnection() {
+ return as2ServerConnection;
+ }
+
+ public Producer createProducer() throws Exception {
+ return new AS2Producer(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 AS2Consumer consumer = new AS2Consumer(this, processor);
+ // also set consumer.* properties
+ configureConsumer(consumer);
+ return consumer;
+ }
+
+ @Override
+ protected ApiMethodPropertiesHelper<AS2Configuration> getPropertiesHelper() {
+ return AS2PropertiesHelper.getHelper();
+ }
+
+ protected String getThreadProfileName() {
+ return AS2Constants.THREAD_PROFILE_NAME;
+ }
+
+ @Override
+ protected void afterConfigureProperties() {
+ // create HTTP connection eagerly, a good way to validate configuration
+ switch (apiName) {
+ case SEND:
+ createAS2ClientConnection();
+ break;
+ case LISTEN:
+ createAS2ServerConnection();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public Object getApiProxy(ApiMethod method, Map<String, Object> args) {
+ if (apiProxy == null) {
+ createApiProxy(method, args);
+ }
+ return apiProxy;
+ }
+
+ /**
+ * Some description of this option, and what it does
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ private void createApiProxy(ApiMethod method, Map<String, Object> args) {
+ switch (apiName) {
+ case SEND:
+ apiProxy = new AS2ClientManager(getAS2ClientConnection());
+ break;
+ case LISTEN:
+ apiProxy = new AS2ServerManager(getAS2ServerConnection());
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid API name " + apiName);
+ }
+ }
+
+ private void createAS2ClientConnection() {
+ try {
+ as2ClientConnection = AS2ConnectionHelper.createAS2ClientConnection(configuration);
+ } catch (UnknownHostException e) {
+ throw new RuntimeCamelException(String.format("Client HTTP connection failed: Unknown target host '%s'",
+ configuration.getTargetHostname()));
+ } catch (IOException e) {
+ throw new RuntimeCamelException("Client HTTP connection failed", e);
+ }
+ }
+
+ private void createAS2ServerConnection() {
+ try {
+ as2ServerConnection = AS2ConnectionHelper.createAS2ServerConnection(configuration);
+ } catch (IOException e) {
+ throw new RuntimeCamelException("Server HTTP connection failed", e);
+ }
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Producer.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Producer.java
new file mode 100644
index 0000000..5fc4a0f
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Producer.java
@@ -0,0 +1,31 @@
+/**
+ * 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.as2;
+
+import org.apache.camel.component.as2.internal.AS2ApiName;
+import org.apache.camel.component.as2.internal.AS2PropertiesHelper;
+import org.apache.camel.util.component.AbstractApiProducer;
+
+/**
+ * The AS2 producer.
+ */
+public class AS2Producer extends AbstractApiProducer<AS2ApiName, AS2Configuration> {
+
+ public AS2Producer(AS2Endpoint endpoint) {
+ super(endpoint, AS2PropertiesHelper.getHelper());
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
new file mode 100644
index 0000000..23e769c
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
@@ -0,0 +1,73 @@
+/**
+ * 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.as2.internal;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.component.as2.AS2Configuration;
+import org.apache.camel.component.as2.api.AS2ClientConnection;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+
+/**
+ * Utility class for creating AS2 connections.
+ */
+public final class AS2ConnectionHelper {
+
+ private static Map<Integer, AS2ServerConnection> serverConnections = new HashMap<Integer, AS2ServerConnection>();
+
+ /**
+ * Prevent instantiation
+ */
+ private AS2ConnectionHelper() {
+ }
+
+ /**
+ * Create an AS2 client connection.
+ *
+ * @param configuration - configuration used to configure connection.
+ * @return The AS2 client connection.
+ * @throws UnknownHostException Failed to establish connection due to unknown host.
+ * @throws IOException - Failed to establish connection.
+ */
+ public static AS2ClientConnection createAS2ClientConnection(AS2Configuration configuration) throws UnknownHostException, IOException {
+ return new AS2ClientConnection(configuration.getAs2Version(), configuration.getUserAgent(), configuration.getClientFqdn(),
+ configuration.getTargetHostname(), configuration.getTargetPortNumber());
+ }
+
+ /**
+ * Create an AS2 server connection.
+ *
+ * @param configuration - configuration used to configure connection.
+ * @return The AS2 server connection.
+ * @throws IOException
+ */
+ public static AS2ServerConnection createAS2ServerConnection(AS2Configuration configuration) throws IOException {
+ synchronized (serverConnections) {
+ AS2ServerConnection serverConnection = serverConnections.get(configuration.getServerPortNumber());
+ if (serverConnection == null) {
+ serverConnection = new AS2ServerConnection(configuration.getAs2Version(), configuration.getServer(),
+ configuration.getServerFqdn(), configuration.getServerPortNumber(),
+ configuration.getSigningCertificateChain(), configuration.getSigningPrivateKey());
+ serverConnections.put(configuration.getServerPortNumber(), serverConnection);
+ }
+ return serverConnection;
+ }
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2Constants.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2Constants.java
new file mode 100644
index 0000000..6edda82
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2Constants.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.as2.internal;
+
+/**
+ * Constants for AS2 component.
+ */
+public interface AS2Constants {
+
+ // suffix for parameters when passed as exchange header properties
+ String PROPERTY_PREFIX = "CamelAS2.";
+
+ // thread profile name for this component
+ String THREAD_PROFILE_NAME = "CamelAS2";
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2PropertiesHelper.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2PropertiesHelper.java
new file mode 100644
index 0000000..18cb741
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2PropertiesHelper.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.as2.internal;
+
+import org.apache.camel.component.as2.AS2Configuration;
+import org.apache.camel.util.component.ApiMethodPropertiesHelper;
+
+/**
+ * Singleton {@link ApiMethodPropertiesHelper} for AS2 component.
+ */
+public final class AS2PropertiesHelper extends ApiMethodPropertiesHelper<AS2Configuration> {
+
+ private static AS2PropertiesHelper helper;
+
+ private AS2PropertiesHelper() {
+ super(AS2Configuration.class, AS2Constants.PROPERTY_PREFIX);
+ }
+
+ public static synchronized AS2PropertiesHelper getHelper() {
+ if (helper == null) {
+ helper = new AS2PropertiesHelper();
+ }
+ return helper;
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/main/resources/META-INF/services/org/apache/camel/component/as2 b/components/camel-as2/camel-as2-component/src/main/resources/META-INF/services/org/apache/camel/component/as2
new file mode 100644
index 0000000..93dff32
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/main/resources/META-INF/services/org/apache/camel/component/as2
@@ -0,0 +1 @@
+class=org.apache.camel.component.as2.AS2Component
diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIntegrationTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIntegrationTest.java
new file mode 100644
index 0000000..a685145
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIntegrationTest.java
@@ -0,0 +1,210 @@
+/**
+ * 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.as2;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2Constants;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+import org.apache.camel.component.as2.api.AS2ServerManager;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIEntity;
+import org.apache.camel.component.as2.api.entity.DispositionNotificationMultipartReportEntity;
+import org.apache.camel.component.as2.api.entity.MimeEntity;
+import org.apache.camel.component.as2.api.util.HttpMessageUtils;
+import org.apache.camel.component.as2.internal.AS2ApiCollection;
+import org.apache.camel.component.as2.internal.AS2ClientManagerApiMethod;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.entity.ContentType;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test class for {@link org.apache.camel.component.as2.api.AS2ClientManager} APIs.
+ */
+public class AS2ClientManagerIntegrationTest extends AbstractAS2TestSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AS2ClientManagerIntegrationTest.class);
+ private static final String PATH_PREFIX = AS2ApiCollection.getCollection().getApiName(AS2ClientManagerApiMethod.class).getName();
+
+ private static final String REQUEST_URI = "/";
+ private static final String SUBJECT = "Test Case";
+ private static final String AS2_NAME = "878051556";
+ private static final String FROM = "mrAS@example.org";
+
+ private static final String MDN_FROM = "as2Test@server.example.com";
+ private static final String MDN_SUBJECT_PREFIX = "MDN Response:";
+
+ private static final String EDI_MESSAGE =
+ "UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'\n"
+ + "UNH+00000000000117+INVOIC:D:97B:UN'\n"
+ + "BGM+380+342459+9'\n"
+ + "DTM+3:20060515:102'\n"
+ + "RFF+ON:521052'\n"
+ + "NAD+BY+792820524::16++CUMMINS MID-RANGE ENGINE PLANT'\n"
+ + "NAD+SE+005435656::16++GENERAL WIDGET COMPANY'\n"
+ + "CUX+1:USD'\n"
+ + "LIN+1++157870:IN'\n"
+ + "IMD+F++:::WIDGET'\n"
+ + "QTY+47:1020:EA'\n"
+ + "ALI+US'\n"
+ + "MOA+203:1202.58'\n"
+ + "PRI+INV:1.179'\n"
+ + "LIN+2++157871:IN'\n"
+ + "IMD+F++:::DIFFERENT WIDGET'\n"
+ + "QTY+47:20:EA'\n"
+ + "ALI+JP'\n"
+ + "MOA+203:410'\n"
+ + "PRI+INV:20.5'\n"
+ + "UNS+S'\n"
+ + "MOA+39:2137.58'\n"
+ + "ALC+C+ABG'\n"
+ + "MOA+8:525'\n"
+ + "UNT+23+00000000000117'\n"
+ + "UNZ+1+00000000000778'\n";
+
+ private static final String EXPECTED_AS2_VERSION = "1.1";
+ private static final String EXPECTED_MDN_SUBJECT = MDN_SUBJECT_PREFIX + SUBJECT;
+
+ private static AS2ServerConnection serverConnection;
+
+ @Test
+ public void plainMessageSendTest() throws Exception {
+ final Map<String, Object> headers = new HashMap<String, Object>();
+ // parameter type is String
+ headers.put("CamelAS2.ediMessage", EDI_MESSAGE);
+ // parameter type is String
+ headers.put("CamelAS2.requestUri", REQUEST_URI);
+ // parameter type is String
+ headers.put("CamelAS2.subject", SUBJECT);
+ // parameter type is String
+ headers.put("CamelAS2.from", FROM);
+ // parameter type is String
+ headers.put("CamelAS2.as2From", AS2_NAME);
+ // parameter type is String
+ headers.put("CamelAS2.as2To", AS2_NAME);
+ // parameter type is org.apache.camel.component.as2.api.AS2MessageStructure
+ headers.put("CamelAS2.as2MessageStructure", AS2MessageStructure.PLAIN);
+ // parameter type is org.apache.http.entity.ContentType
+ headers.put("CamelAS2.ediMessageContentType", ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII));
+ // parameter type is String
+ headers.put("CamelAS2.ediMessageTransferEncoding", null);
+ // parameter type is String
+ headers.put("CamelAS2.signingAlgorithmName", null);
+ // parameter type is java.security.cert.Certificate[]
+ headers.put("CamelAS2.signingCertificateChain", null);
+ // parameter type is java.security.PrivateKey
+ headers.put("CamelAS2.signingPrivateKey", null);
+ // parameter type is String
+ headers.put("CamelAS2.dispositionNotificationTo", "mrAS2@example.com");
+ // parameter type is String[]
+ headers.put("CamelAS2.signedReceiptMicAlgorithms", null);
+
+ final org.apache.http.protocol.HttpCoreContext result = requestBodyAndHeaders("direct://SEND", null, headers);
+
+ assertNotNull("send result", result);
+ LOG.debug("send: " + result);
+ HttpRequest request = result.getRequest();
+ assertNotNull("Request", request);
+ assertTrue("Request does not contain body", request instanceof HttpEntityEnclosingRequest);
+ HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request body", entity);
+ assertTrue("Request body does not contain EDI entity", entity instanceof ApplicationEDIEntity);
+ String ediMessage = ((ApplicationEDIEntity)entity).getEdiMessage();
+ assertEquals("EDI message is different", EDI_MESSAGE, ediMessage);
+
+ HttpResponse response = result.getResponse();
+ assertNotNull("Response", response);
+ assertEquals("Unexpected response type", AS2MimeType.MULTIPART_REPORT, HttpMessageUtils.getHeaderValue(response, AS2Header.CONTENT_TYPE));
+ assertEquals("Unexpected mime version", AS2Constants.MIME_VERSION, HttpMessageUtils.getHeaderValue(response, AS2Header.MIME_VERSION));
+ assertEquals("Unexpected AS2 version", EXPECTED_AS2_VERSION, HttpMessageUtils.getHeaderValue(response, AS2Header.AS2_VERSION));
+ assertEquals("Unexpected MDN subject", EXPECTED_MDN_SUBJECT, HttpMessageUtils.getHeaderValue(response, AS2Header.SUBJECT));
+ assertEquals("Unexpected MDN from", MDN_FROM, HttpMessageUtils.getHeaderValue(response, AS2Header.FROM));
+ assertEquals("Unexpected AS2 from", AS2_NAME, HttpMessageUtils.getHeaderValue(response, AS2Header.AS2_FROM));
+ assertEquals("Unexpected AS2 to", AS2_NAME, HttpMessageUtils.getHeaderValue(response, AS2Header.AS2_TO));
+ assertNotNull("Missing message id", HttpMessageUtils.getHeaderValue(response, AS2Header.MESSAGE_ID));
+
+ HttpEntity responseEntity = response.getEntity();
+ assertNotNull("Response entity", responseEntity);
+ assertTrue("Unexpected response entity type", responseEntity instanceof DispositionNotificationMultipartReportEntity);
+ DispositionNotificationMultipartReportEntity reportEntity = (DispositionNotificationMultipartReportEntity)responseEntity;
+ assertEquals("Unexpected number of body parts in report", 2, reportEntity.getPartCount());
+ MimeEntity firstPart = reportEntity.getPart(0);
+ assertEquals("Unexpected content type in first body part of report", ContentType.create(AS2MimeType.TEXT_PLAIN, AS2Charset.US_ASCII).toString(), firstPart.getContentTypeValue());
+ MimeEntity secondPart = reportEntity.getPart(1);
+ assertEquals("Unexpected content type in second body part of report",
+ ContentType.create(AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION, AS2Charset.US_ASCII).toString(),
+ secondPart.getContentTypeValue());
+ }
+
+ @BeforeClass
+ public static void setupTest() throws Exception {
+ receiveTestMessages();
+ }
+
+ @AfterClass
+ public static void teardownTest() throws Exception {
+ if (serverConnection != null) {
+ serverConnection.stopListening("/");
+ }
+ }
+
+ public static class RequestHandler implements HttpRequestHandler {
+
+ @Override
+ public void handle(HttpRequest request, HttpResponse response, HttpContext context)
+ throws HttpException, IOException {
+ LOG.info("Received test message: " + request);
+ context.setAttribute(AS2ServerManager.FROM, MDN_FROM);
+ context.setAttribute(AS2ServerManager.SUBJECT, MDN_SUBJECT_PREFIX);
+ }
+
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ public void configure() {
+ // test route for send
+ from("direct://SEND").to("as2://" + PATH_PREFIX + "/send");
+
+ }
+ };
+ }
+
+ private static void receiveTestMessages() throws IOException {
+ serverConnection = new AS2ServerConnection("1.1", "AS2ClientManagerIntegrationTest Server",
+ "server.example.com", 8888, null, null);
+ serverConnection.listen("/", new RequestHandler());
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIntegrationTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIntegrationTest.java
new file mode 100644
index 0000000..272adbe
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIntegrationTest.java
@@ -0,0 +1,310 @@
+/**
+ * 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.as2;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.as2.api.AS2Charset;
+import org.apache.camel.component.as2.api.AS2ClientConnection;
+import org.apache.camel.component.as2.api.AS2ClientManager;
+import org.apache.camel.component.as2.api.AS2Header;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationPkcs7SignatureEntity;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
+import org.apache.camel.component.as2.api.util.SigningUtils;
+import org.apache.camel.component.as2.internal.AS2ApiCollection;
+import org.apache.camel.component.as2.internal.AS2ServerManagerApiMethod;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpVersion;
+import org.apache.http.entity.ContentType;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpCoreContext;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
+import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
+import org.bouncycastle.asn1.smime.SMIMECapability;
+import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
+import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test class for {@link org.apache.camel.component.as2.api.AS2ServerManager} APIs.
+ */
+public class AS2ServerManagerIntegrationTest extends AbstractAS2TestSupport {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AS2ServerManagerIntegrationTest.class);
+ private static final String PATH_PREFIX = AS2ApiCollection.getCollection().getApiName(AS2ServerManagerApiMethod.class).getName();
+
+ private static final String METHOD = "POST";
+ private static final String TARGET_HOST = "localhost";
+ private static final int TARGET_PORT = 8888;
+ private static final String AS2_VERSION = "1.1";
+ private static final String USER_AGENT = "Camel AS2 Endpoint";
+ private static final String REQUEST_URI = "/";
+ private static final String AS2_NAME = "878051556";
+ private static final String SUBJECT = "Test Case";
+ private static final String FROM = "mrAS@example.org";
+ private static final String CLIENT_FQDN = "example.org";
+ private static final String DISPOSITION_NOTIFICATION_TO = "mrAS@example.org";
+ private static final String[] SIGNED_RECEIPT_MIC_ALGORITHMS = new String[] {"sha1", "md5"};
+
+ private static final String EDI_MESSAGE = "UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'\n"
+ + "UNH+00000000000117+INVOIC:D:97B:UN'\n"
+ + "BGM+380+342459+9'\n"
+ + "DTM+3:20060515:102'\n"
+ + "RFF+ON:521052'\n"
+ + "NAD+BY+792820524::16++CUMMINS MID-RANGE ENGINE PLANT'\n"
+ + "NAD+SE+005435656::16++GENERAL WIDGET COMPANY'\n"
+ + "CUX+1:USD'\n"
+ + "LIN+1++157870:IN'\n"
+ + "IMD+F++:::WIDGET'\n"
+ + "QTY+47:1020:EA'\n"
+ + "ALI+US'\n"
+ + "MOA+203:1202.58'\n"
+ + "PRI+INV:1.179'\n"
+ + "LIN+2++157871:IN'\n"
+ + "IMD+F++:::DIFFERENT WIDGET'\n"
+ + "QTY+47:20:EA'\n"
+ + "ALI+JP'\n"
+ + "MOA+203:410'\n"
+ + "PRI+INV:20.5'\n"
+ + "UNS+S'\n"
+ + "MOA+39:2137.58'\n"
+ + "ALC+C+ABG'\n"
+ + "MOA+8:525'\n"
+ + "UNT+23+00000000000117'\n"
+ + "UNZ+1+00000000000778'";
+
+ private AS2SignedDataGenerator gen;
+
+ private KeyPair issueKP;
+ private X509Certificate issueCert;
+
+ private KeyPair signingKP;
+ private X509Certificate signingCert;
+ private List<X509Certificate> certList;
+
+ @Test
+ public void receivePlainEDIMessageTest() throws Exception {
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME, AS2MessageStructure.PLAIN,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII), null, null, null,
+ DISPOSITION_NOTIFICATION_TO, SIGNED_RECEIPT_MIC_ALGORITHMS);
+
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:as2RcvMsgs");
+ mockEndpoint.expectedMinimumMessageCount(1);
+ mockEndpoint.setResultWaitTime(TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS));
+ mockEndpoint.assertIsSatisfied();
+
+ final List<Exchange> exchanges = mockEndpoint.getExchanges();
+ assertNotNull("listen result", exchanges);
+ assertFalse("listen result", exchanges.isEmpty());
+ LOG.debug("poll result: " + exchanges);
+
+ Exchange exchange = exchanges.get(0);
+ Message message = exchange.getIn();
+ assertNotNull("exchange message", message);
+ BasicHttpContext context = message.getBody(BasicHttpContext.class);
+ assertNotNull("context", context);
+ HttpCoreContext coreContext = HttpCoreContext.adapt(context);
+ HttpRequest request = coreContext.getRequest();
+ assertNotNull("request", request);
+ assertEquals("Unexpected method value", METHOD, request.getRequestLine().getMethod());
+ assertEquals("Unexpected request URI value", REQUEST_URI, request.getRequestLine().getUri());
+ assertEquals("Unexpected HTTP version value", HttpVersion.HTTP_1_1, request.getRequestLine().getProtocolVersion());
+ assertEquals("Unexpected subject value", SUBJECT, request.getFirstHeader(AS2Header.SUBJECT).getValue());
+ assertEquals("Unexpected from value", FROM, request.getFirstHeader(AS2Header.FROM).getValue());
+ assertEquals("Unexpected AS2 version value", AS2_VERSION, request.getFirstHeader(AS2Header.AS2_VERSION).getValue());
+ assertEquals("Unexpected AS2 from value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_FROM).getValue());
+ assertEquals("Unexpected AS2 to value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_TO).getValue());
+ assertTrue("Unexpected message id value", request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN + ">"));
+ assertEquals("Unexpected target host value", TARGET_HOST + ":" + TARGET_PORT, request.getFirstHeader(AS2Header.TARGET_HOST).getValue());
+ assertEquals("Unexpected user agent value", USER_AGENT, request.getFirstHeader(AS2Header.USER_AGENT).getValue());
+ assertNotNull("Date value missing", request.getFirstHeader(AS2Header.DATE));
+ assertNotNull("Content length value missing", request.getFirstHeader(AS2Header.CONTENT_LENGTH));
+ assertTrue("Unexpected content type for message", request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+
+
+ assertTrue("Request does not contain entity", request instanceof BasicHttpEntityEnclosingRequest);
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request does not contain entity", entity);
+ assertTrue("Unexpected request entity type", entity instanceof ApplicationEDIFACTEntity);
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) entity;
+ assertTrue("Unexpected content type for entity", ediEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+ assertTrue("Entity not set as main body of request", ediEntity.isMainBody());
+
+ }
+
+ @Test
+ public void receiveMultipartSignedMessageTest() throws Exception {
+ setupSigningGenerator();
+
+ AS2ClientConnection clientConnection = new AS2ClientConnection(AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST, TARGET_PORT);
+ AS2ClientManager clientManager = new AS2ClientManager(clientConnection);
+
+ clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME, AS2MessageStructure.SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT, AS2Charset.US_ASCII), null,
+ certList.toArray(new Certificate[0]), signingKP.getPrivate(), DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS);
+
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:as2RcvMsgs");
+ mockEndpoint.expectedMinimumMessageCount(1);
+ mockEndpoint.setResultWaitTime(TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS));
+ mockEndpoint.assertIsSatisfied();
+
+ final List<Exchange> exchanges = mockEndpoint.getExchanges();
+ assertNotNull("listen result", exchanges);
+ assertFalse("listen result", exchanges.isEmpty());
+ LOG.debug("poll result: " + exchanges);
+
+ Exchange exchange = exchanges.get(0);
+ Message message = exchange.getIn();
+ assertNotNull("exchange message", message);
+ BasicHttpContext context = message.getBody(BasicHttpContext.class);
+ assertNotNull("context", context);
+ HttpCoreContext coreContext = HttpCoreContext.adapt(context);
+ HttpRequest request = coreContext.getRequest();
+ assertNotNull("request", request);
+ assertEquals("Unexpected method value", METHOD, request.getRequestLine().getMethod());
+ assertEquals("Unexpected request URI value", REQUEST_URI, request.getRequestLine().getUri());
+ assertEquals("Unexpected HTTP version value", HttpVersion.HTTP_1_1, request.getRequestLine().getProtocolVersion());
+
+ assertEquals("Unexpected subject value", SUBJECT, request.getFirstHeader(AS2Header.SUBJECT).getValue());
+ assertEquals("Unexpected from value", FROM, request.getFirstHeader(AS2Header.FROM).getValue());
+ assertEquals("Unexpected AS2 version value", AS2_VERSION, request.getFirstHeader(AS2Header.AS2_VERSION).getValue());
+ assertEquals("Unexpected AS2 from value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_FROM).getValue());
+ assertEquals("Unexpected AS2 to value", AS2_NAME, request.getFirstHeader(AS2Header.AS2_TO).getValue());
+ assertTrue("Unexpected message id value", request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN + ">"));
+ assertEquals("Unexpected target host value", TARGET_HOST + ":" + TARGET_PORT, request.getFirstHeader(AS2Header.TARGET_HOST).getValue());
+ assertEquals("Unexpected user agent value", USER_AGENT, request.getFirstHeader(AS2Header.USER_AGENT).getValue());
+ assertNotNull("Date value missing", request.getFirstHeader(AS2Header.DATE));
+ assertNotNull("Content length value missing", request.getFirstHeader(AS2Header.CONTENT_LENGTH));
+ assertTrue("Unexpected content type for message", request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.MULTIPART_SIGNED));
+
+ assertTrue("Request does not contain entity", request instanceof BasicHttpEntityEnclosingRequest);
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)request).getEntity();
+ assertNotNull("Request does not contain entity", entity);
+ assertTrue("Unexpected request entity type", entity instanceof MultipartSignedEntity);
+ MultipartSignedEntity signedEntity = (MultipartSignedEntity)entity;
+ assertTrue("Entity not set as main body of request", signedEntity.isMainBody());
+ assertTrue("Request contains invalid number of mime parts", signedEntity.getPartCount() == 2);
+
+ // Validated first mime part.
+ assertTrue("First mime part incorrect type ", signedEntity.getPart(0) instanceof ApplicationEDIFACTEntity);
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) signedEntity.getPart(0);
+ assertTrue("Unexpected content type for first mime part", ediEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT));
+ assertFalse("First mime type set as main body of request", ediEntity.isMainBody());
+
+ // Validate second mime part.
+ assertTrue("Second mime part incorrect type ", signedEntity.getPart(1) instanceof ApplicationPkcs7SignatureEntity);
+ ApplicationPkcs7SignatureEntity signatureEntity = (ApplicationPkcs7SignatureEntity) signedEntity.getPart(1);
+ assertTrue("Unexpected content type for second mime part", signatureEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_PKCS7_SIGNATURE));
+ assertFalse("First mime type set as main body of request", signatureEntity.isMainBody());
+
+ // Validate Signature
+ assertTrue("Signature is invalid", signedEntity.isValid());
+ }
+
+ private void setupSigningGenerator() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+
+ setupKeysAndCertificates();
+
+ // Create and populate certificate store.
+ JcaCertStore certs = new JcaCertStore(certList);
+
+ // Create capabilities vector
+ SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
+ capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
+ capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
+ capabilities.addCapability(SMIMECapability.dES_CBC);
+
+ // Create signing attributes
+ ASN1EncodableVector attributes = new ASN1EncodableVector();
+ attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(new IssuerAndSerialNumber(new X500Name(signingCert.getIssuerDN().getName()), signingCert.getSerialNumber())));
+ attributes.add(new SMIMECapabilitiesAttribute(capabilities));
+
+ gen = SigningUtils.createSigningGenerator(certList.toArray(new X509Certificate[0]), signingKP.getPrivate());
+ gen.addCertificates(certs);
+
+ }
+
+ private void setupKeysAndCertificates() throws Exception {
+ //
+ // set up our certificates
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+
+ String issueDN = "O=Punkhorn Software, C=US";
+ issueKP = kpg.generateKeyPair();
+ issueCert = Utils.makeCertificate(
+ issueKP, issueDN, issueKP, issueDN);
+
+ //
+ // certificate we sign against
+ //
+ String signingDN = "CN=William J. Collins, E=punkhornsw@gmail.com, O=Punkhorn Software, C=US";
+ signingKP = kpg.generateKeyPair();
+ signingCert = Utils.makeCertificate(
+ signingKP, signingDN, issueKP, issueDN);
+
+ certList = new ArrayList<X509Certificate>();
+
+ certList.add(signingCert);
+ certList.add(issueCert);
+
+ }
+
+
+ @Override
+ protected RouteBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ public void configure() {
+ // test route for listen
+ from("as2://" + PATH_PREFIX + "/listen?requestUriPattern=/")
+ .to("mock:as2RcvMsgs");
+
+ }
+ };
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AbstractAS2TestSupport.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AbstractAS2TestSupport.java
new file mode 100644
index 0000000..691bf0c
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AbstractAS2TestSupport.java
@@ -0,0 +1,82 @@
+/**
+ * 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.as2;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.test.junit4.CamelTestSupport;
+import org.apache.camel.util.IntrospectionSupport;
+
+/**
+ * Abstract base class for AS2 Integration tests generated by Camel API component maven plugin.
+ */
+public class AbstractAS2TestSupport extends CamelTestSupport {
+
+ private static final String TEST_OPTIONS_PROPERTIES = "/test-options.properties";
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+
+ final CamelContext context = super.createCamelContext();
+
+ // read AS2 component configuration from TEST_OPTIONS_PROPERTIES
+ final Properties properties = new Properties();
+ try {
+ properties.load(getClass().getResourceAsStream(TEST_OPTIONS_PROPERTIES));
+ } catch (Exception e) {
+ throw new IOException(String.format("%s could not be loaded: %s", TEST_OPTIONS_PROPERTIES, e.getMessage()),
+ e);
+ }
+
+ Map<String, Object> options = new HashMap<String, Object>();
+ for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+ options.put(entry.getKey().toString(), entry.getValue());
+ }
+
+ final AS2Configuration configuration = new AS2Configuration();
+ IntrospectionSupport.setProperties(configuration, options);
+
+ // add AS2Component to Camel context
+ final AS2Component component = new AS2Component(context);
+ component.setConfiguration(configuration);
+ context.addComponent("as2", component);
+
+ return context;
+ }
+
+ @Override
+ public boolean isCreateCamelContextPerClass() {
+ // only create the context once for this class
+ return true;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <T> T requestBodyAndHeaders(String endpointUri, Object body, Map<String, Object> headers)
+ throws CamelExecutionException {
+ return (T) template().requestBodyAndHeaders(endpointUri, body, headers);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected <T> T requestBody(String endpoint, Object body) throws CamelExecutionException {
+ return (T) template().requestBody(endpoint, body);
+ }
+}
diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/Utils.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/Utils.java
new file mode 100644
index 0000000..1dcc0ff
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/Utils.java
@@ -0,0 +1,83 @@
+/**
+ * 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.as2;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+
+public final class Utils {
+ //
+ // certificate serial number seed.
+ //
+ static int serialNo = 1;
+
+ private Utils() {
+ }
+
+ public static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey pub) throws IOException {
+ SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pub.getEncoded());
+
+ BcX509ExtensionUtils utils = new BcX509ExtensionUtils();
+ return utils.createAuthorityKeyIdentifier(info);
+ }
+
+ public static SubjectKeyIdentifier createSubjectKeyId(PublicKey pub) throws IOException {
+ SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pub.getEncoded());
+
+ return new BcX509ExtensionUtils().createSubjectKeyIdentifier(info);
+ }
+
+ /**
+ * create a basic X509 certificate from the given keys
+ */
+ public static X509Certificate makeCertificate(KeyPair subKP, String subDN, KeyPair issKP, String issDN)
+ throws GeneralSecurityException, IOException, OperatorCreationException {
+ PublicKey subPub = subKP.getPublic();
+ PrivateKey issPriv = issKP.getPrivate();
+ PublicKey issPub = issKP.getPublic();
+
+ X509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(new X500Name(issDN),
+ BigInteger.valueOf(serialNo++), new Date(System.currentTimeMillis()),
+ new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 100)), new X500Name(subDN), subPub);
+
+ v3CertGen.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyId(subPub));
+
+ v3CertGen.addExtension(Extension.authorityKeyIdentifier, false, createAuthorityKeyId(issPub));
+
+ return new JcaX509CertificateConverter().setProvider("BC").getCertificate(
+ v3CertGen.build(new JcaContentSignerBuilder("MD5withRSA").setProvider("BC").build(issPriv)));
+ }
+
+}
diff --git a/components/camel-as2/camel-as2-component/src/test/resources/log4j2.properties b/components/camel-as2/camel-as2-component/src/test/resources/log4j2.properties
new file mode 100644
index 0000000..d9f0508
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/resources/log4j2.properties
@@ -0,0 +1,23 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------
+
+appender.out.type = Console
+appender.out.name = out
+appender.out.layout.type = PatternLayout
+appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n
+rootLogger.level = INFO
+rootLogger.appenderRef.out.ref = out
diff --git a/components/camel-as2/camel-as2-component/src/test/resources/test-options.properties b/components/camel-as2/camel-as2-component/src/test/resources/test-options.properties
new file mode 100644
index 0000000..10b4e62
--- /dev/null
+++ b/components/camel-as2/camel-as2-component/src/test/resources/test-options.properties
@@ -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.
+## ---------------------------------------------------------------------------
+
+###############################################
+## AS2 Component Configuration Parameters
+###############################################
+
+## user agent identification string sent by endpoint
+## in User-Agent message header
+#userAgent=AS2 Client
+
+## host name of host messages sent to
+targetHostname=localhost
+
+## port number of host messages sent to
+targetPortNumber=8888
+
+## fully qualified domain name used in Message-Id message header.
+clientFqdn=example.com
+
+## port number listened on
+serverPortNumber=8888
\ No newline at end of file
diff --git a/components/camel-as2/pom.xml b/components/camel-as2/pom.xml
new file mode 100644
index 0000000..0cc73d8
--- /dev/null
+++ b/components/camel-as2/pom.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <artifactId>components</artifactId>
+ <groupId>org.apache.camel</groupId>
+ <version>2.22.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-as2-parent</artifactId>
+ <packaging>pom</packaging>
+
+ <name>Camel :: AS2 :: Parent</name>
+ <description>Camel AS2 parent</description>
+
+ <modules>
+ <module>camel-as2-component</module>
+ <module>camel-as2-api</module>
+ </modules>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/components/pom.xml b/components/pom.xml
index 9cb9add..d1857e7 100644
--- a/components/pom.xml
+++ b/components/pom.xml
... 20 lines suppressed ...
--
To stop receiving notification emails like this one, please contact
davsclaus@apache.org.