diff options
author | Christof Rabensteiner <christof.rabensteiner@iaik.tugraz.at> | 2019-05-27 09:46:36 +0200 |
---|---|---|
committer | Christof Rabensteiner <christof.rabensteiner@iaik.tugraz.at> | 2019-05-27 10:28:56 +0200 |
commit | 695ab1f836160d40c4352a2c3127f4f687912817 (patch) | |
tree | cd23bf0e2f6430bd2f5caf40825705e3fe644f98 | |
parent | 0a316ada10bb88720dd15958168409fcb9fcf800 (diff) | |
download | moa-zs-695ab1f836160d40c4352a2c3127f4f687912817.tar.gz moa-zs-695ab1f836160d40c4352a2c3127f4f687912817.tar.bz2 moa-zs-695ab1f836160d40c4352a2c3127f4f687912817.zip |
Intercept Incoming DeliveryRequestStatus and Store as byte[]
- Add egovutils dependency (Reason: Need DomUtils to serialize /
unserialize Soap Message via DOMParser)
- Add Incerceptor to MsgClient / -Factory that stores the message
content byte-by-byte in the DeliveryRepository. The format is
required for successfully validating a DeliveryRequestStatus.
- Add SoapUtils, which interacts with byte[] Soap message.
- Add CXFMessageUtils, which interacts with CXF Messages from
interceptor chains.
- Refactor xsd namespaces: Move them out from the PrefixMapper and
into a dedicated class.
9 files changed, 257 insertions, 71 deletions
@@ -96,6 +96,13 @@ <artifactId>javax.jws-api</artifactId> <version>${jwsapi.version}</version> </dependency> + <!-- egov utils. + checkout https://gitlab.iaik.tugraz.at/egiz/egovutils/commits/2.0.7 and run mvn install --> + <dependency> + <groupId>at.gv.util</groupId> + <artifactId>egovutils</artifactId> + <version>2.0.7-snapshot</version> + </dependency> </dependencies> <build> diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java index 6f0b1d9..fd36a92 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClient.java @@ -6,6 +6,8 @@ import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; import org.apache.cxf.jaxws.JaxWsClientFactoryBean; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.PhaseInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -15,53 +17,34 @@ public class MsgClient { private final DeliveryRequestType msgRequest; private final ConfigType config; + private final PhaseInterceptor<? extends Message> interceptor; - MsgClient(DeliveryRequestType msgRequest, ConfigType config) { + MsgClient(DeliveryRequestType msgRequest, ConfigType config, PhaseInterceptor<? extends Message> interceptor) { this.msgRequest = msgRequest; this.config = config; + this.interceptor = interceptor; } + /** + * Send {@code msgRequest} to {@code Config/Server/ZUSEUrlID} and run {@code interceptor} on response. + * @return + */ public DeliveryRequestStatusType send() { - var proxy = connect(config); - return proxy.delivery(msgRequest); - } private App2ZusePort connect(ConfigType config) { - new JaxWsClientFactoryBean(); - var address = config.getServer().getZUSEUrlID(); - var factory = new JaxWsProxyFactoryBean(); + + var factory = new JaxWsClientFactoryBean(); factory.setServiceClass(App2ZusePort.class); factory.setAddress(address); + factory.getInInterceptors().add(interceptor); - var proxy = factory.create(); - -// var client = ClientProxy.getClient(proxy); -// var conduit = (HTTPConduit) client.getConduit(); -// -// if (addressIsHttps(address)) { -// var tlsParams = new TLSClientParameters(); -// tlsParams.setSSLSocketFactory(createSSLContext().getSocketFactory()); -// conduit.setTlsClientParameters(tlsParams); -// } + var proxy = new JaxWsProxyFactoryBean(factory).create(); return (App2ZusePort) proxy; } - -// private SSLContext createSSLContext() { -// java.util.Properties props = new Properties(); -// props.entrySet(); -// -// // return SSLUtils.getPropertiesSSLContext(this.props, this.configDir, this.propsPrefix, forceTrustAllManager); -// return null; -// } - - private boolean addressIsHttps(String address) { - return address.startsWith("https://"); - } - } diff --git a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java index 9884bd5..82468bc 100644 --- a/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java +++ b/src/main/java/at/gv/egiz/moazs/msg/MsgClientFactory.java @@ -2,13 +2,21 @@ package at.gv.egiz.moazs.msg; import at.gv.zustellung.app2mzs.xsd.ConfigType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class MsgClientFactory { + private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; + + @Autowired + public MsgClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor) { + this.storeResponseInterceptor = storeResponseInterceptor; + } + public MsgClient create(DeliveryRequestType msgRequest, ConfigType config) { - return new MsgClient(msgRequest, config); + return new MsgClient(msgRequest, config, storeResponseInterceptor); } } diff --git a/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java b/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java new file mode 100644 index 0000000..4e023ac --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java @@ -0,0 +1,55 @@ +package at.gv.egiz.moazs.msg; + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.SOAPUtils; +import at.gv.egiz.moazs.util.CXFMessageUtils; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; + +@Component +public class StoreSOAPBodyBinaryInRepositoryInterceptor extends AbstractPhaseInterceptor<Message> { + + private static final Logger log = LoggerFactory.getLogger(StoreSOAPBodyBinaryInRepositoryInterceptor.class); + + private final CXFMessageUtils messageUtils; + private final SOAPUtils soapUtils; + private final DeliveryRepository repository; + + @Autowired + public StoreSOAPBodyBinaryInRepositoryInterceptor(CXFMessageUtils extractor, SOAPUtils soapUtils, + DeliveryRepository repository) { + super(Phase.RECEIVE); + this.messageUtils = extractor; + this.soapUtils = soapUtils; + this.repository = repository; + } + + public void handleMessage(Message message) { + + try { + byte[] content = messageUtils.copyContent(message); + + if(content.length > 0) { + Element document = soapUtils.toDOM(content); + byte[] status = soapUtils.unwrapSoapEnvelope(document); + String appDeliveryId = soapUtils.getAppDeliveryIDFrom(document); + repository.addSignedDeliveryRequestStatus(status, appDeliveryId); + log.info("Store binary DeliveryRequestStatus with AppDeliveryId={}", appDeliveryId); + } + } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { + throw new MoaZSException("Could not extract signed data from message.", e); + } + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MoaZSPrefixMapper.java b/src/main/java/at/gv/egiz/moazs/scheme/MoaZSPrefixMapper.java index faee8a5..d725ce6 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/MoaZSPrefixMapper.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/MoaZSPrefixMapper.java @@ -12,43 +12,21 @@ public class MoaZSPrefixMapper extends NamespacePrefixMapper { private final Map<String, String> map = new HashMap<>(); public MoaZSPrefixMapper() { - map.put(getMsgNamespaceUri(), "msg"); - map.put(getMsgPNamespaceUri(), "msgp"); - map.put(getMzsNamespaceUri(), "mzs"); - map.put(getMzsPNamespaceUri(), "mzsp"); - map.put(getDsigNamespaceUri(), "dsig"); + map.put(NameSpace.MSG, "msg"); + map.put(NameSpace.MSGP, "msgp"); + map.put(NameSpace.MZS, "mzs"); + map.put(NameSpace.MZSP, "mzsp"); + map.put(NameSpace.DSIG, "dsig"); } public MoaZSPrefixMapper(boolean isMzs) { this(); - map.put((isMzs) ? getMzsPNamespaceUri() : getMsgPNamespaceUri(), "p"); - map.put((isMzs) ? getMzsNamespaceUri() : getMsgNamespaceUri(), ""); + map.put((isMzs) ? NameSpace.MZSP : NameSpace.MSGP, "p"); + map.put((isMzs) ? NameSpace.MZS : NameSpace.MSG , ""); } @Override public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) { return map.getOrDefault(namespaceUri, suggestion); } - - private String getMsgNamespaceUri() { - return new at.gv.zustellung.msg.xsd.ObjectFactory().createDeliveryRequest(null).getName().getNamespaceURI(); - } - - private String getMsgPNamespaceUri() { - return new at.gv.zustellung.msg.xsd.persondata.ObjectFactory().createPerson(null).getName().getNamespaceURI(); - } - - private String getMzsNamespaceUri() { - return new at.gv.zustellung.app2mzs.xsd.ObjectFactory().createDeliveryRequest(null).getName().getNamespaceURI(); - } - - private String getMzsPNamespaceUri() { - return new at.gv.zustellung.app2mzs.xsd.persondata.ObjectFactory().createAbstractPersonData(null).getName().getNamespaceURI(); - } - - private String getDsigNamespaceUri() { - return new org.w3._2000._09.xmldsig_.ObjectFactory().createCanonicalizationMethod(null).getName().getNamespaceURI(); - } - - } diff --git a/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java b/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java new file mode 100644 index 0000000..63276cb --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/NameSpace.java @@ -0,0 +1,17 @@ +package at.gv.egiz.moazs.scheme; + +public class NameSpace { + + private NameSpace() {} + + public static final String MSG = new at.gv.zustellung.msg.xsd.ObjectFactory().createDeliveryRequest(null).getName().getNamespaceURI(); + + public static final String MSGP = new at.gv.zustellung.msg.xsd.persondata.ObjectFactory().createPerson(null).getName().getNamespaceURI(); + + public static final String MZS = new at.gv.zustellung.app2mzs.xsd.ObjectFactory().createDeliveryRequest(null).getName().getNamespaceURI(); + + public static final String MZSP = new at.gv.zustellung.app2mzs.xsd.persondata.ObjectFactory().createAbstractPersonData(null).getName().getNamespaceURI(); + + public static final String DSIG = new org.w3._2000._09.xmldsig_.ObjectFactory().createCanonicalizationMethod(null).getName().getNamespaceURI(); + +} diff --git a/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java b/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java new file mode 100644 index 0000000..2d4df4b --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/SOAPUtils.java @@ -0,0 +1,44 @@ +package at.gv.egiz.moazs.scheme; + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.util.DOMUtils; +import org.apache.cxf.binding.soap.Soap11; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Component +public class SOAPUtils { + + public Element toDOM(byte[] bytes) throws IOException, SAXException, ParserConfigurationException { + var stream = new ByteArrayInputStream(bytes); + return DOMUtils.parseXmlNonValidating(stream); + } + + public byte[] unwrapSoapEnvelope(Element document) { + try { + var body = document.getElementsByTagNameNS(Soap11.SOAP_NAMESPACE, "Body"); + var item = body.item(0).getFirstChild(); + + return DOMUtils.serializeNode(item, true) + .getBytes(StandardCharsets.UTF_8); + + } catch (IOException | TransformerException e) { + throw new MoaZSException("Error while parsing message. ", e); + } + } + + public String getAppDeliveryIDFrom(Element document) { + var elements = document.getElementsByTagNameNS(NameSpace.MSG, "AppDeliveryID"); + + var appDeliveryIdElement = elements.item(0).getFirstChild(); + + return appDeliveryIdElement.getNodeValue(); + } +} diff --git a/src/main/java/at/gv/egiz/moazs/util/CXFMessageUtils.java b/src/main/java/at/gv/egiz/moazs/util/CXFMessageUtils.java new file mode 100644 index 0000000..07fdfb6 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/CXFMessageUtils.java @@ -0,0 +1,92 @@ +package at.gv.egiz.moazs.util; + +import org.apache.cxf.helpers.IOUtils; +import org.apache.cxf.io.CachedOutputStream; +import org.apache.cxf.io.CachedWriter; +import org.apache.cxf.io.DelegatingInputStream; +import org.apache.cxf.message.Message; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.SequenceInputStream; + +@Component +public class CXFMessageUtils { + + private static final Logger LOG = LoggerFactory.getLogger(CXFMessageUtils.class); + + /** + * Copy and return content of org.apache.cxf.message.Message; Restore InputStream / Reader from + * message.getContent() after consuming it. + * @param message + * @return + * @throws IOException + */ + public byte[] copyContent(Message message) throws IOException { + + if (message.getContent(InputStream.class) != null) { + return streamMessageContentToByteArray(message); + } else if (message.getContent(Reader.class) != null) { + return readMessageContentToByteArray(message); + } else { + LOG.warn("Message Content is neither Reader nor InputStream; will return empty content."); + return new byte[0]; + } + } + + /** + * Source: Adapted from ApacheCXF 3.3.0 / LoggingInInterceptor.logInputStream() + * @param message + * @return copy of message content + * @throws IOException + */ + private byte[] streamMessageContentToByteArray(Message message) throws IOException { + + var in = message.getContent(InputStream.class); + + try (var out = new CachedOutputStream()) { + + // use the appropriate input stream and restore it later + InputStream bis = in instanceof DelegatingInputStream + ? ((DelegatingInputStream)in).getInputStream() : in; + + //only copy up to the limit since that's all we need to log + //we can stream the rest + IOUtils.copy(bis, out); + out.flush(); + bis = new SequenceInputStream(out.getInputStream(), bis); + + // restore the delegating input stream or the input stream + if (in instanceof DelegatingInputStream) { + ((DelegatingInputStream)in).setInputStream(bis); + } else { + message.setContent(InputStream.class, bis); + } + + return out.getBytes(); + } + + } + + /** + * Source: Adapted from ApacheCXF 3.3.0 / LoggingInInterceptor.logReader() + * @param message + * @return copy of message content + * @throws IOException + */ + private byte[] readMessageContentToByteArray(Message message) throws IOException { + var reader = message.getContent(Reader.class); + var writer = new CachedWriter(); + IOUtils.copyAndCloseInput(reader, writer); + message.setContent(Reader.class, writer.getReader()); + + var buffer = new StringBuilder(); + writer.writeCacheTo(buffer); + return buffer.toString().getBytes(); + } + +} diff --git a/src/test/java/at/gv/egiz/moazs/MsgClientTest.java b/src/test/java/at/gv/egiz/moazs/MsgClientTest.java index 9d79435..1822ff5 100644 --- a/src/test/java/at/gv/egiz/moazs/MsgClientTest.java +++ b/src/test/java/at/gv/egiz/moazs/MsgClientTest.java @@ -6,7 +6,6 @@ import at.gv.egiz.moazs.scheme.Marshaller; import at.gv.zustellung.app2mzs.xsd.ConfigType; import at.gv.zustellung.msg.xsd.DeliveryRequestType; import at.gv.zustellung.msg.xsd.ObjectFactory; -import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,8 +22,8 @@ import static at.gv.zustellung.app2mzs.xsd.ConfigType.configTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.ServerType.serverTypeBuilder; -//@RunWith(SpringRunner.class) -//@SpringBootTest +// @RunWith(SpringRunner.class) +// @SpringBootTest public class MsgClientTest { private final static Logger logger = LoggerFactory.getLogger(MsgClient.class); @@ -34,27 +33,30 @@ public class MsgClientTest { private final String basePath = "src/test/resources/at/gv/egiz/moazs/MsgClientTest/"; - private MsgClientFactory factory = new MsgClientFactory(); - @Autowired private Marshaller msgMarshaller; + @Autowired + private MsgClientFactory factory; + private static final ObjectFactory OF = new ObjectFactory(); // this test requires that a zusemsg service runs under httpServiceUri! - - //tmp disabled. todo: set up integration tests - //@Test + // tmp disabled. todo: set up integration tests + // @Test public void sendValidMessage() throws IOException { var request = loadFromFile("validDeliveryRequest.xml"); var config = generateConfig(httpServiceUri); var client = factory.create(request, config); - var status = client.send(); - - logger.info("status: " + msgMarshaller.marshallXml(OF.createDeliveryRequestStatus(status))); + try{ + var status = client.send(); + logger.info("status: " + msgMarshaller.marshallXml(OF.createDeliveryResponse(status))); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } } //@Test |