Unmarshal/Convert JSON data to JAXBElement object

Last Updated on by

Post summary: How to marshal and unmarshal JAXBElement to JSON with Jackson.

This post gives solution for following usecase

Usecase

XML document -> POJO containing JAXBElement -> JSON -> POJO containing JAXBElement.

For some reason, there is a POJO which has some JAXBElement. This usually happens when mixing SOAP and REST services with XML and JSON. This POJO is easily converted to JSON data. Then from this JSON data, a POJO containing JAXBElement has to be unmarshalled.

Problem

By default Jackson’s ObjectMapper is unable to unmarshal JSON data into a JAXBElement object. An exception is thrown:

No suitable constructor found for type [simple type, class javax.xml.bind.JAXBElement]: cannot instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)

Solution

Although somewhere it is recommended to use com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule it might not work. The solution is to create custom MixIn and register it with ObjectMapper. MixIn class is:

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

@JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil"})
public abstract class JAXBElementMixIn<T> {

	@JsonCreator
	public JAXBElementMixIn(@JsonProperty("name") QName name,
			@JsonProperty("declaredType") Class<T> declaredType,
			@JsonProperty("scope") Class scope,
			@JsonProperty("value") T value) {
	}
}

ObjectMapper is instantiated with following code:

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);

Conclusion

Jackson’s ObjectMapper does not support JSON to JAXBElement conversion by default. This is solved by creating a custom MixIn as described in the current post and register it with ObjectMapper.

Related Posts

Read more...

Send SOAP request over HTTPS without valid certificates

Last Updated on by

Post summary: How to send SOAP request over HTTPS in Java without generating and installing certificates. NB: This MUST not be used for production code!

SOAP (Simple Object Access Protocol) is a protocol used in web services. It allows exchanging of XML data over HTTP or HTTPS.

Send SOAP over HTTP

Sending SOAP message over HTTP is Java is as simple as:

public SOAPMessage sendSoapRequest(String endpointUrl, SOAPMessage request) {
	try {
		// Send HTTP SOAP request and get response
		SOAPConnection soapConnection
				= SOAPConnectionFactory.newInstance().createConnection();
		SOAPMessage response = soapConnection.call(request, endpointUrl);
		// Close connection
		soapConnection.close();
		return response;
	} catch (SOAPException ex) {
		// Do Something
	}
	return null;
}

HTTPS

HTTPS is an HTTP over a security layer (SSL/TLS). This is the essence of secure internet communication. For valid HTTPS connection server needs a valid certificate signed by a certification authority. Establishing an HTTPS connection between client and server is by a procedure called SSL handshake in which client validates server certificate and both set session key which they use to encrypt messages. This level of security makes the code above to fail if executed against HTTPS host:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative DNS name matching localhost found.

This error appears because the server is running on localhost and there is no valid certificate for localhost. The proper way for handling this is to generate a valid or test SSL certificate with IP or hostname of the machine running the server and install it on it.

Trust all hosts

Generating and installing SSL certificates for test servers is a good idea but is not worth the effort. So in order to overcome this, an HTTPS connection should be opened and it should be instructed to trust any hostname. First is to add dummy implementation of HostnameVerifier interface trusting all hosts:

/**
 * Dummy class implementing HostnameVerifier to trust all host names
 */
private static class TrustAllHosts implements HostnameVerifier {
	public boolean verify(String hostname, SSLSession session) {
		return true;
	}
}

Open HTTPS connection

Opening HTTPS connection is done with Java’s HttpsURLConnection. Instruction to trust all hosts is done with setHostnameVerifier(new TrustAllHosts()) method. The re-factored code is:

public SOAPMessage sendSoapRequest(String endpointUrl, SOAPMessage request) {
	try {
		final boolean isHttps = endpointUrl.toLowerCase().startsWith("https");
		HttpsURLConnection httpsConnection = null;
		// Open HTTPS connection
		if (isHttps) {
			// Open HTTPS connection
			URL url = new URL(endpointUrl);
			httpsConnection = (HttpsURLConnection) url.openConnection();
			// Trust all hosts
			httpsConnection.setHostnameVerifier(new TrustAllHosts());
			// Connect
			httpsConnection.connect();
		}
		// Send HTTP SOAP request and get response
		SOAPConnection soapConnection
				= SOAPConnectionFactory.newInstance().createConnection();
		SOAPMessage response = soapConnection.call(request, endpointUrl);
		// Close connection
		soapConnection.close();
		// Close HTTPS connection
		if (isHttps) {
			httpsConnection.disconnect();
		}
		return response;
	} catch (SOAPException | IOException ex) {
		// Do Something
	}
	return null;
}

Not valid certificate exception

Running code above throws an exception which generally means that server is either missing an SSL certificate or its SSL certificate is not valid, i.e. not signed by a certification authority. Error is:

javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

The proper way to handle this is to add server’s certificate to client’s JVM TrustStore certificates. But since test servers may change and server on which client is running may also change generating of certificates is an overhead. Since we are writing test code it is OK to lower the level of security of SSL.

Trust all certificates

Trusting all certificates is a very bad practice and MUST never be used in production code. This is undermining the whole concept and purpose of SSL certificates. For test code is not that bad to do this sin. A class implementing X509TrustManager interface is needed:

/**
 * Dummy class implementing X509TrustManager to trust all certificates
 */
private static class TrustAllCertificates implements X509TrustManager {
	public void checkClientTrusted(X509Certificate[] certs, String authType) {
	}

	public void checkServerTrusted(X509Certificate[] certs, String authType) {
	}

	public X509Certificate[] getAcceptedIssuers() {
		return null;
	}
}

Create SSL context trusting all certificates

Instructing HttpsURLConnection to trust all certificates is done with following code:

// Create SSL context and trust all certificates
SSLContext sslContext = SSLContext.getInstance("SSL");
TrustManager[] trustAll = new TrustManager[] {new TrustAllCertificates()};
sslContext.init(null, trustAll, new java.security.SecureRandom());
// Set trust all certificates context to HttpsURLConnection
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

Send SOAP over HTTPS without having valid certificates

The final code is:

/**
 * Sends SOAP request and saves it in a queue.
 *
 * @param request SOAP Message request object
 * @return SOAP Message response object
 */
public SOAPMessage sendSoapRequest(String endpointUrl, SOAPMessage request) {
	try {
		final boolean isHttps = endpointUrl.toLowerCase().startsWith("https");
		HttpsURLConnection httpsConnection = null;
		// Open HTTPS connection
		if (isHttps) {
			// Create SSL context and trust all certificates
			SSLContext sslContext = SSLContext.getInstance("SSL");
			TrustManager[] trustAll
					= new TrustManager[] {new TrustAllCertificates()};
			sslContext.init(null, trustAll, new java.security.SecureRandom());
			// Set trust all certificates context to HttpsURLConnection
			HttpsURLConnection
					.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
			// Open HTTPS connection
			URL url = new URL(endpointUrl);
			httpsConnection = (HttpsURLConnection) url.openConnection();
			// Trust all hosts
			httpsConnection.setHostnameVerifier(new TrustAllHosts());
			// Connect
			httpsConnection.connect();
		}
		// Send HTTP SOAP request and get response
		SOAPConnection soapConnection
				= SOAPConnectionFactory.newInstance().createConnection();
		SOAPMessage response = soapConnection.call(request, endpointUrl);
		// Close connection
		soapConnection.close();
		// Close HTTPS connection
		if (isHttps) {
			httpsConnection.disconnect();
		}
		return response;
	} catch (SOAPException | IOException
			| NoSuchAlgorithmException | KeyManagementException ex) {
		// Do Something
	}
	return null;
}

Conclusion

Although this code is very handy and eases a lot testing of SOAP over HTTPS it MUST never be used for production purpose!

Read more...