Tutorial: file attachments in SOAP requests

6 min read
This article wants to explain a simple way to attach a file to an XML request as an inline field using Spring-WS.

You can refer to this article to view how to create a WS using annotations.

Step by step:

  1. create the contract or the XSD representation of the entities involved

  2. create the Java code to map these entities

  3. create the marshalling context (we'll use Castor in this case – it allows a more flexible configuration as you can use interfaces in your code. With JAXB, you can't. Castor is not mandatory for this tutorial, you can use also JAXB, but I want to show the XML mappings for a binary file). You can refere to the official Spring-WS documentation to know how to wire-up Castor in Spring context.


First we need to create the types, the request and the response in our service contract. We need an Attachment, AttachmentContent (I divided them to allow to retrieve a list of Attachments without sending all the binaries), a LoadAttachmentContentByAttachmentIdRequest and the LoadAttachmentContentByAttachmentIdResponse. I used also an abstract class Entity to store the ID.

That is the XSD excerpt:
[xml]
<complexType name="Entity" abstract="true">
<sequence>
<element name="id" type="string"></element>
</sequence>
</complexType>

<complexType name="Attachment">
<complexContent>
<extension base="indesk:Entity">
<sequence>
<element name="name" type="string"></element>
<element name="extension" type="string"></element>
<element name="contentLength" type="int"></element>
<element name="contentMIMEType" type="string"></element>
</sequence>
</extension>
</complexContent>
</complexType>

<complexType name="AttachmentContent">
<complexContent>
<extension base="indesk:Entity">
<sequence>
<element name="content" type="hexBinary"></element>
</sequence>
</extension>
</complexContent>
</complexType>

<complexType
name="LoadAttachmentContentByAttachmentIdRequestType">
<sequence>
<element name="attachmentId" type="string"></element>
</sequence>
</complexType>

<complexType
name="LoadAttachmentContentByAttachmentIdResponseType">
<complexContent>
<extension base="indesk:GenericResponse">
<sequence>
<element name="result" type="indesk:AttachmentContent"></element>
</sequence>
</complexType>

<element name="LoadAttachmentContentByAttachmentIdRequest"
type="indesk:LoadAttachmentContentByAttachmentIdRequestType">
</element>

<element name="LoadAttachmentContentByAttachmentIdResponse"
type="indesk:LoadAttachmentContentByAttachmentIdResponseType">
</element>
[/xml]

Now we can create the classes in Java code

[java]
public abstract class Entity
{
private String id;

public void setId(String id)
{
this.id = id;
}

public String getId()
{
return id;
}

}


@SuppressWarnings("serial")
public class Attachment
{
private String extension;
private String name;
private int contentLength;
private String contentMIMEType;

public String fullName() {
return this.getName() + "." + this.getExtension();
}

public String getExtension() {
return extension;
}

public String getName() {
return name;
}

public void setExtension(String extension) {
this.extension = extension;
}

public void setName(String name) {
this.name = name;
}

public int getContentLength() {
return contentLength;
}

public String getContentMIMEType() {
return contentMIMEType;
}

public void setContentLength(int contentLength) {
this.contentLength = contentLength;
}

public void setContentMIMEType(String mimeType) {
this.contentMIMEType = mimeType;
}
}


@SuppressWarnings("serial")
public class AttachmentContent
{
private byte[] content;

/**
*
*/
public AttachmentContent()
{
super();
}

public byte[] getContent()
{
return content;
}

public void setContent(byte[] content)
{
this.content = content;
}

public void putContentString(String content)
{
this.setContent(StringUtils.hexStringToByteArray(content));
}

public String retrieveContentString()
{
try
{
return StringUtils.getHexStringFromBytes(getContent());
}
catch (UnsupportedEncodingException e) {
logger.error(getContent(), e);
return "";
}
}
}

public class LoadAttachmentContentByAttachmentIdRequest
{
private String attachmentId;

public void setAttachmentId(String attachmentId)
{
this.attachmentId = attachmentId;
}

public String getAttachmentId()
{
return attachmentId;
}
}

public class LoadAttachmentContentByAttachmentIdResponse
{
private IAttachmentContent result;

public void setResult(IAttachmentContent result)
{
this.result = result;
}

public IAttachmentContent getResult()
{
return result;
}
}
[/java]

We've got the classes. Now, we must say to Castor how to marshal them.

[java]
<mapping>
<class name="[package].Attachment" auto-complete="false">
<map-to xml="Attachment" ns-uri="http://www.tempuri.org" />
<field name="id" type="string">
<bind-xml name="id" node="element" />
</field>
<field name="name" type="string">
<bind-xml name="name" node="element" />
</field>
<field name="extension" type="string">
<bind-xml name="extension" node="element" />
</field>
<field name="contentLength" type="int">
<bind-xml name="contentLength" node="element" />
</field>
<field name="contentMIMEType" type="string">
<bind-xml name="contentMIMEType" node="element" />
</field>
</class>

<class name="[package].AttachmentContent" auto-complete="false">
<map-to xml="AttachmentContent" ns-uri="http://www.tempuri.org" />
<field name="id" type="string">
<bind-xml name="id" node="element" />
</field>
<field name="content" type="string" get-method="retrieveContentString"
set-method="putContentString">
<bind-xml name="content" node="element" />
</field>
</class>

<class name="[package].LoadAttachmentContentByAttachmentIdRequest"
auto-complete="false">
<map-to xml="LoadAttachmentContentByAttachmentIdRequest"
ns-uri="http://www.tempuri.org" />
<field name="attachmentId" type="string">
<bind-xml name="attachmentId" node="element" />
</field>
</class>

<class name="[package].LoadAttachmentContentByAttachmentIdResponse"
auto-complete="false">
<map-to xml="LoadAttachmentContentByAttachmentIdResponse"
ns-uri="http://www.tempuri.org" />
<field name="exception" type="string">
<bind-xml name="exception" node="element" />
</field>
<field name="message" type="string">
<bind-xml name="message" node="element" />
</field>
<field name="succeded" type="boolean">
<bind-xml name="succeded" node="element" />
</field>
</class>
</mapping>
[/java]
Done.