package org.jentrata.ebms.as4.internal.routes; import org.apache.camel.Exchange; import org.apache.camel.LoggingLevel; import org.apache.camel.Processor; import org.apache.camel.ValidationException; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.builder.xml.Namespaces; import org.apache.camel.component.freemarker.FreemarkerConstants; import org.jentrata.ebms.*; import org.jentrata.ebms.cpa.pmode.Security; import org.jentrata.ebms.internal.messaging.MessageDetector; import org.jentrata.ebms.messaging.MessageStore; import org.jentrata.ebms.messaging.SplitAttachmentsToBody; import org.jentrata.ebms.messaging.XmlSchemaValidator; import org.jentrata.ebms.soap.SoapMessageDataFormat; import org.jentrata.ebms.soap.SoapPayloadProcessor; import org.jentrata.ebms.utils.EbmsUtils; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import javax.xml.XMLConstants; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import java.io.File; import java.io.InputStream; /** * Exposes an HTTP endpoint that consumes AS4 Messages * * @author aaronwalker */ public class EbMS3InboundRouteBuilder extends RouteBuilder { private String ebmsHttpEndpoint = "jetty:http://0.0.0.0:8081/jentrata/ebms/inbound"; private String ebmsResponseInbound = "activemq:queue:jentrata_internal_ebms_response"; private String ebmsDLQ = null; private String inboundEbmsQueue = "activemq:queue:jentrata_internal_ebms_inbound"; private String inboundEbmsPayloadQueue = "activemq:queue:jentrata_internal_ebms_inbound_payload"; private String inboundEbmsSignalsQueue = "activemq:queue:jentrata_internal_ebms_inbound_signals"; private String securityErrorQueue = "activemq:queue:jentrata_internal_ebms_error"; private String messgeStoreEndpoint = MessageStore.DEFAULT_MESSAGE_STORE_ENDPOINT; private String messageInsertEndpoint = MessageStore.DEFAULT_MESSAGE_INSERT_ENDPOINT; private String messageUpdateEndpoint = MessageStore.DEFAULT_MESSAGE_UPDATE_ENDPOINT; private String wsseSecurityCheck = "direct:wsseSecurityCheck"; private String validateTradingPartner = "direct:validatePartner"; private MessageDetector messageDetector; private SoapPayloadProcessor payloadProcessor; private XmlSchemaValidator xmlSchemaValidator; @Override public void configure() throws Exception { final Namespaces ns = new Namespaces("S12", "http://www.w3.org/2003/05/soap-envelope") .add("eb3", "http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/"); if(ebmsDLQ != null && ebmsDLQ.length() > 0) { deadLetterChannel(ebmsDLQ).useOriginalMessage(); } from(ebmsHttpEndpoint) .streamCaching() .removeHeaders("Jentrata*") .log(LoggingLevel.INFO, "Request:${headers}") .log(LoggingLevel.DEBUG, "Request Body:\n${body}") .choice() .when(header(Exchange.HTTP_METHOD).isNotEqualTo("POST")) .log(LoggingLevel.INFO, "Ignoring request, received HTTP Method other than POST ") .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(405)) .setHeader("Allow", constant("POST")) .to("direct:errorHandler") .otherwise() .doTry() .to("direct:inboundInternal") .doCatch(ValidationException.class) .setHeader(EbmsConstants.MESSAGE_STATUS, constant(MessageStatusType.FAILED)) .setHeader(EbmsConstants.MESSAGE_STATUS_DESCRIPTION, simple("${exception.message}")) .setHeader(EbmsConstants.EBMS_ERROR, constant(EbmsError.EBMS_0001)) .setHeader(EbmsConstants.EBMS_ERROR_CODE, constant(EbmsError.EBMS_0001.getErrorCode())) .setHeader(EbmsConstants.EBMS_ERROR_DESCRIPTION, simple("exception.message")) .to(messageUpdateEndpoint) .to("direct:errorHandler") .doCatch(Exception.class) .log(LoggingLevel.DEBUG, "headers:${headers}\nbody:\n${in.body}") .log(LoggingLevel.ERROR, "${exception.message}\n${exception.stacktrace}") .setHeader(EbmsConstants.MESSAGE_STATUS, constant(MessageStatusType.FAILED)) .setHeader(EbmsConstants.MESSAGE_STATUS_DESCRIPTION, simple("${exception.message}")) .to(messageUpdateEndpoint) .to("direct:errorHandler") .doFinally() .process(new Processor() { @Override public void process(Exchange exchange) throws Exception { exchange.getOut().setHeader(EbmsConstants.CONTENT_TYPE, EbmsConstants.SOAP_XML_CONTENT_TYPE); exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE)); exchange.getOut().setHeader("X-Jentrata-Version", System.getProperty("jentrataVersion", "DEV")); exchange.getOut().setBody(exchange.getIn().getBody()); } }) .end() .end() .end() .routeId("_jentataInboundHttp"); from(ebmsResponseInbound, "direct:inboundInternal") .streamCaching() .setHeader(EbmsConstants.MESSAGE_DIRECTION, constant(EbmsConstants.MESSAGE_DIRECTION_INBOUND)) .bean(messageDetector, "parse") //Determine what type of message it is for example SOAP 1.1 or SOAP 1.2 ebms2 or ebms3 etc .choice() .when(header(EbmsConstants.MESSAGE_ID).isNull()) .throwException(new EbmsException(EbmsError.EBMS_0004, "Invalid ebms message, eb:MessageId not found")) .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.UNKNOWN.name())) .throwException(new EbmsException(EbmsError.EBMS_0004, "Invalid ebms message, request does not contain valid ebms message")) .end() .log(LoggingLevel.INFO, "Received ebms Message msgId:${headers.JentrataMessageID} - type:${headers.JentrataMessageType}") .to(messgeStoreEndpoint) //essentially we claim check the raw incoming message/payload .unmarshal(new SoapMessageDataFormat()) //extract the SOAP Envelope as set it has the message body .setHeader(EbmsConstants.MESSAGE_STATUS, constant(MessageStatusType.RECEIVED.name())) .to("direct:validateEbmsHeader") .setHeader(EbmsConstants.MESSAGE_TO, ns.xpath("//eb3:To/eb3:PartyId/text()", String.class)) .setHeader(EbmsConstants.MESSAGE_FROM, ns.xpath("//eb3:From/eb3:PartyId/text()", String.class)) .setHeader(EbmsConstants.MESSAGE_SERVICE, ns.xpath("//eb3:CollaborationInfo/eb3:Service/text()", String.class)) .setHeader(EbmsConstants.MESSAGE_ACTION, ns.xpath("//eb3:CollaborationInfo/eb3:Action/text()", String.class)) .setHeader(EbmsConstants.MESSAGE_CONVERSATION_ID, ns.xpath("//eb3:CollaborationInfo/eb3:ConversationId/text()", String.class)) .to("direct:lookupCpaId") .setHeader(EbmsConstants.MESSAGE_RECEIPT_PATTERN, simple("${headers.JentrataCPA.security.sendReceiptReplyPattern.name()}")) .setHeader(EbmsConstants.MESSAGE_DUP_DETECTION, simple("${headers.JentrataCPA.receptionAwareness.duplicateDetectionEnabled}")) .to("direct:setHttpResponseCode") .to(messageInsertEndpoint) //create a message entry in the message store to track the state of the message .to("direct:securityCheck") .choice() .when(header(EbmsConstants.SECURITY_CHECK).isEqualTo(Boolean.FALSE)) .to("direct:handleSecurityException") .otherwise() .to("direct:processPayloads") .to(messageUpdateEndpoint) .wireTap(EventNotificationRouteBuilder.SEND_NOTIFICATION_ENDPOINT) .to("direct:removeHeaders") .setHeader("X-Jentrata-Version", simple("${sys.jentrataVersion}")) .setHeader(EbmsConstants.CONTENT_TYPE, constant(EbmsConstants.SOAP_XML_CONTENT_TYPE)) .routeId("_jentrataEbmsInbound"); from("direct:validateEbmsHeader") .choice() .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.USER_MESSAGE)) .setHeader("soapMessage", body()) .setBody(xpath("//*[local-name()='Messaging']")) .convertBodyTo(InputStream.class) .process(xmlSchemaValidator) .setBody(header("soapMessage")) .removeHeader("soapMessage") .end() .end() .routeId("_jentrataValidateEbmsHeader"); from("direct:securityCheck") .choice() .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.SIGNAL_MESSAGE_ERROR)) .setHeader(EbmsConstants.SECURITY_CHECK, constant(true)) .otherwise() .to(validateTradingPartner) .to(wsseSecurityCheck) .routeId("_jentrataSecurityCheck"); from("direct:processPayloads") .convertBodyTo(String.class) .choice() .when(simple("${headers.JentrataMessageType} != 'USER_MESSAGE' && ${headers.JentrataIsDuplicateMessage}")) .log(LoggingLevel.INFO,"Ignoring duplicate ${headers.JentrataMessageType} msgId:${headers.JentrataMessageID}") .setHeader(EbmsConstants.MESSAGE_STATUS, constant(MessageStatusType.IGNORED.name())) .setHeader(EbmsConstants.MESSAGE_STATUS_DESCRIPTION,simple("duplicate of ${headers.JentrataMessageID}")) .setHeader(Exchange.HTTP_RESPONSE_CODE,constant(204)) .setHeader(EbmsConstants.MESSAGE_ID,header(EbmsConstants.DUPLICATE_MESSAGE_ID)) .setBody(constant(null)) .otherwise() .choice() .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.SIGNAL_MESSAGE_ERROR.name())) .inOnly(inboundEbmsSignalsQueue) .setBody(constant(null)) .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.SIGNAL_MESSAGE.name())) .inOnly(inboundEbmsSignalsQueue) .setBody(constant(null)) .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.SIGNAL_MESSAGE_WITH_USER_MESSAGE.name())) .inOnly(inboundEbmsSignalsQueue) .setBody(constant(null)) .when(header(EbmsConstants.MESSAGE_TYPE).isEqualTo(MessageType.USER_MESSAGE.name())) .choice() .when(simple("${headers.JentrataIsDuplicateMessage} && ${headers.JentrataIsDuplicateMessage}")) .log(LoggingLevel.INFO,"Ignoring duplicate ${headers.JentrataMessageType} msgId:${headers.JentrataMessageID} only generating a signal message") .setHeader(EbmsConstants.MESSAGE_STATUS, constant(MessageStatusType.IGNORED.name())) .setHeader(EbmsConstants.MESSAGE_STATUS_DESCRIPTION,simple("duplicate of ${headers.JentrataMessageID}")) .setHeader("JentrataOriginalMessageID",header(EbmsConstants.MESSAGE_ID)) .setHeader(EbmsConstants.MESSAGE_ID,header(EbmsConstants.DUPLICATE_MESSAGE_ID)) .to(messageUpdateEndpoint) .setHeader(EbmsConstants.MESSAGE_ID,header("JentrataOriginalMessageID")) .to("direct:generateReceipt") .otherwise() .to("direct:processUserMessage") .end() .end() .end() .end() .routeId("_jentrataEbmsPayloadProcessing"); from("direct:processUserMessage") .setProperty("JentrataMessageBody",body()) .setHeader(SplitAttachmentsToBody.ORIGINAL_MESSAGE_BODY, body()) .setBody(xpath("//*[local-name()='Body']/*[1]")) .convertBodyTo(String.class) .split(new SplitAttachmentsToBody(true, false, true)) .bean(payloadProcessor) .choice() .when(header(EbmsConstants.COMPRESSION_TYPE).isEqualTo(EbmsConstants.GZIP)) .unmarshal().gzip() .setHeader(EbmsConstants.CONTENT_TYPE, header("MimeType")) .removeHeader(SplitAttachmentsToBody.ORIGINAL_MESSAGE_BODY) .inOnly(inboundEbmsPayloadQueue) .otherwise() .removeHeader(SplitAttachmentsToBody.ORIGINAL_MESSAGE_BODY) .inOnly(inboundEbmsPayloadQueue) .end() .end() .setBody(property("JentrataMessageBody")) .to("direct:generateReceipt") .routeId("_jentrataProcessUserMessage"); from("direct:generateReceipt") .choice() .when(header(EbmsConstants.MESSAGE_RECEIPT_PATTERN).isEqualTo(Security.ReplyPatternType.Callback.name())) .inOnly(inboundEbmsQueue) .setBody(constant(null)) .otherwise() .to(inboundEbmsQueue) .end() .routeId("_jentrataGenerateReceipt"); from("direct:handleSecurityException") .choice() .when(header(EbmsConstants.MESSAGE_RECEIPT_PATTERN).isEqualTo(Security.ReplyPatternType.Callback.name())) .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(204)) .otherwise() .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500)) .end() .setHeader(EbmsConstants.EBMS_ERROR_CODE, constant(EbmsError.EBMS_0101.getErrorCode())) .setHeader(EbmsConstants.EBMS_ERROR_DESCRIPTION, simple("${headers.JentrataSecurityResults?.message}")) .setHeader(EbmsConstants.SECURITY_ERROR_CODE, simple("${headers.JentrataSecurityResults?.errorCode}")) .log(LoggingLevel.INFO, "Security Exception for msgId:${headers.JentrataMessageID} - errorCode:${headers.JentrataSecurityErrorCode} - ${headers.JentrataEbmsErrorDesc}") .wireTap(EventNotificationRouteBuilder.SEND_NOTIFICATION_ENDPOINT) .to("direct:generateEbmsError") .routeId("_jentrataHandleSecurityException"); from("direct:errorHandler") .setHeader("CamelException", simple("${exception.message}")) .setHeader("CamelExceptionStackTrace",simple("${exception.stacktrace}")) .setHeader("X-JentrataVersion", simple("${sys.jentrataVersion}")) .choice() .when(header(EbmsConstants.EBMS_VALIDATION_ERROR).isNotNull()) .log(LoggingLevel.INFO, "Validation Exception for msgId:${headers.JentrataMessageID} - errors:${headers.JentrataValidationError}") .setHeader(EbmsConstants.EBMS_ERROR_CODE, simple("headers.JentrataValidationError[0]?.error.errorCode")) .setHeader(EbmsConstants.EBMS_ERROR_DESCRIPTION, simple("${headers.JentrataValidationError[0]?.description}")) .to("direct:generateEbmsError") .convertBodyTo(String.class, "UTF-8") .when(header(EbmsConstants.EBMS_ERROR).isNotNull()) .log(LoggingLevel.INFO, "Ebms Error for msgId:${headers.JentrataMessageID} - errors:${headers.JentrataEbmsError}") .to("direct:generateEbmsError") .convertBodyTo(String.class, "UTF-8") .when(header(Exchange.HTTP_RESPONSE_CODE).isEqualTo(405)) .setHeader(FreemarkerConstants.FREEMARKER_RESOURCE_URI, simple("html/${headers.CamelHttpResponseCode}.html")) .setHeader(EbmsConstants.CONTENT_TYPE, constant("text/html")) .to("freemarker:html/500.html") .convertBodyTo(String.class) .otherwise() .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(500)) .wireTap(EventNotificationRouteBuilder.SEND_NOTIFICATION_ENDPOINT) .setHeader(EbmsConstants.CONTENT_TYPE, constant(EbmsConstants.SOAP_XML_CONTENT_TYPE)) .to("freemarker:templates/soap-fault.ftl") .convertBodyTo(String.class, "UTF-8") .routeId("_jentrataErrorHandler"); from("direct:removeHeaders") .removeHeaders("Jentrata*") .removeHeaders("Accept*") .removeHeaders("Content*") .removeHeader("Host") .removeHeader("User-Agent") .removeHeader("Origin") .removeHeader("Cookie") .removeHeader("JSESSIONID") .removeHeader("breadcrumbId") .routeId("_jentrataRemoveHeaders"); from("direct:generateEbmsError") .wireTap(EventNotificationRouteBuilder.SEND_NOTIFICATION_ENDPOINT) .convertBodyTo(String.class) .choice() .when(header(EbmsConstants.MESSAGE_RECEIPT_PATTERN).isEqualTo(Security.ReplyPatternType.Callback.name())) .inOnly(securityErrorQueue) .setBody(constant(null)) .otherwise() .to(securityErrorQueue) .end() .setHeader(EbmsConstants.CONTENT_TYPE, constant(EbmsConstants.SOAP_XML_CONTENT_TYPE)) .routeId("_jentrataGenerateEbmsError"); from("direct:setHttpResponseCode") .choice() .when(header(EbmsConstants.MESSAGE_RECEIPT_PATTERN).isEqualTo(Security.ReplyPatternType.Callback.name())) .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(204)) .otherwise() .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(200)) .end() .routeId("_jentrataSetHttpResponseCode"); } public String getEbmsHttpEndpoint() { return ebmsHttpEndpoint; } public void setEbmsHttpEndpoint(String ebmsHttpEndpoint) { this.ebmsHttpEndpoint = ebmsHttpEndpoint; } public String getEbmsResponseInbound() { return ebmsResponseInbound; } public void setEbmsResponseInbound(String ebmsResponseInbound) { this.ebmsResponseInbound = ebmsResponseInbound; } public String getEbmsDLQ() { return ebmsDLQ; } public void setEbmsDLQ(String ebmsDLQ) { this.ebmsDLQ = ebmsDLQ; } public String getInboundEbmsQueue() { return inboundEbmsQueue; } public void setInboundEbmsQueue(String inboundEbmsQueue) { this.inboundEbmsQueue = inboundEbmsQueue; } public String getInboundEbmsPayloadQueue() { return inboundEbmsPayloadQueue; } public void setInboundEbmsPayloadQueue(String inboundEbmsPayloadQueue) { this.inboundEbmsPayloadQueue = inboundEbmsPayloadQueue; } public String getInboundEbmsSignalsQueue() { return inboundEbmsSignalsQueue; } public void setInboundEbmsSignalsQueue(String inboundEbmsSignalsQueue) { this.inboundEbmsSignalsQueue = inboundEbmsSignalsQueue; } public String getSecurityErrorQueue() { return securityErrorQueue; } public void setSecurityErrorQueue(String securityErrorQueue) { this.securityErrorQueue = securityErrorQueue; } public String getMessgeStoreEndpoint() { return messgeStoreEndpoint; } public void setMessgeStoreEndpoint(String messgeStoreEndpoint) { this.messgeStoreEndpoint = messgeStoreEndpoint; } public String getMessageInsertEndpoint() { return messageInsertEndpoint; } public void setMessageInsertEndpoint(String messageInsertEndpoint) { this.messageInsertEndpoint = messageInsertEndpoint; } public String getMessageUpdateEndpoint() { return messageUpdateEndpoint; } public void setMessageUpdateEndpoint(String messageUpdateEndpoint) { this.messageUpdateEndpoint = messageUpdateEndpoint; } public String getValidateTradingPartner() { return validateTradingPartner; } public void setValidateTradingPartner(String validateTradingPartner) { this.validateTradingPartner = validateTradingPartner; } public String getWsseSecurityCheck() { return wsseSecurityCheck; } public void setWsseSecurityCheck(String wsseSecurityCheck) { this.wsseSecurityCheck = wsseSecurityCheck; } public MessageDetector getMessageDetector() { return messageDetector; } public void setMessageDetector(MessageDetector messageDetector) { this.messageDetector = messageDetector; } public SoapPayloadProcessor getPayloadProcessor() { return payloadProcessor; } public void setPayloadProcessor(SoapPayloadProcessor payloadProcessor) { this.payloadProcessor = payloadProcessor; } public XmlSchemaValidator getXmlSchemaValidator() { return xmlSchemaValidator; } public void setXmlSchemaValidator(XmlSchemaValidator xmlSchemaValidator) { this.xmlSchemaValidator = xmlSchemaValidator; } }