In the examples so far, the SOAP messages contain text that is converted to service-appropriate
types such as List<Prediction>
. The type conversion is typically automatic, occurring in the
JAX-WS infrastructure without application intervention, but handlers could use JAX-B and related
technologies for converting text to and from Java types. Even a very simple example illustrates
the power of this underlying, automatic conversion. Here is a SOAP response from the
RandService
, in particular a call to the next1 operation, which returns a randomly
generated integer:
<
S:
Envelope
xmlns:
S
=
"http://schemas.xmlsoap.org/soap/envelope/"
>
<
S:
Body
>
<
ns2:
next1Response
xmlns:
ns2
=
"http://rand/"
>
<
return
>-
1691660782
</
return
>
</
ns2:
next1Response
>
</
S:
Body
>
</
S:
Envelope
>
The returned value -1691660782
occurs as text in the SOAP message but is converted automatically
to an int
for a Java client, which does not need to do any explicit type conversion.
Type conversions come to the forefront in the issue of how binary data such as images, movies, and the like can be arguments passed to or values returned from SOAP-based service operations. SOAP-based services can deal with binary payloads but such payloads raise issues of efficiency. There are two general approaches to dealing with binary data in SOAP-based services:
In the course of SOAP development, there have been three options for attachments. SwA (SOAP with
Attachments) is the original specification but does not work well with document
-style services, which
are the default. Moreover, some frameworks such as DotNet do not support SwA out of the box.
DIME (Direct Internet
Message Encapsulation) is a lightweight but proprietary encoding scheme, which has received little play
outside of Windows. MTOM
(Message Transmission Optimization Mechanism), which is based on XOP (XML-Binary Optimized Packaging),
has the W3C seal of approval and enjoys widespread support. In short, MTOM is a modern, efficient,
and interoperable way to share binary data through SOAP-based services.
To underscore the efficiency of MTOM, the first example uses base64 encoding. The
SkiImageService
class (see Example 5-11) has two @WebMethod
operations: getImage returns a specified image about skiing,
for instance, a picture of a nordic skier; GetImageList returns a list of the available
skiing images.
Example 5-11. The SkiImageService
, which delivers images encoded as base64 text
package
images
;
import
javax.jws.WebService
;
import
javax.jws.WebMethod
;
import
java.util.Map
;
import
java.util.HashMap
;
import
java.util.Set
;
import
java.util.List
;
import
java.util.ArrayList
;
import
java.util.Iterator
;
import
java.awt.Image
;
import
java.io.FileInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.ByteArrayInputStream
;
import
javax.imageio.ImageIO
;
import
javax.imageio.stream.ImageInputStream
;
import
javax.imageio.ImageReader
;
@WebService
public
class
SkiImageService
{
private
Map
<
String
,
String
>
photos
;
@WebMethod
public
Image
getImage
(
String
name
)
{
return
createImage
(
name
);
}
@WebMethod
public
List
<
Image
>
getImages
()
{
return
createImageList
();
}
public
SkiImageService
()
{
photos
=
new
HashMap
<
String
,
String
>();
photos
.
put
(
"nordic"
,
"nordic.jpg"
);
photos
.
put
(
"alpine"
,
"alpine.jpg"
);
photos
.
put
(
"telemk"
,
"telemk.jpg"
);
}
private
Image
createImage
(
String
name
)
{
String
fileName
=
photos
.
get
(
name
);
byte
[
]
bytes
=
getRawBytes
(
fileName
);
ByteArrayInputStream
in
=
new
ByteArrayInputStream
(
bytes
);
Iterator
iterators
=
ImageIO
.
getImageReadersByFormatName
(
"jpeg"
);
ImageReader
iterator
=
(
ImageReader
)
iterators
.
next
();
Image
image
=
null
;
try
{
ImageInputStream
iis
=
ImageIO
.
createImageInputStream
(
in
);
iterator
.
setInput
(
iis
,
true
);
image
=
iterator
.
read
(
0
);
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
return
image
;
}
private
List
<
Image
>
createImageList
()
{
List
<
Image
>
list
=
new
ArrayList
<
Image
>();
for
(
String
key
:
photos
.
keySet
())
{
Image
image
=
createImage
(
key
);
if
(
image
!=
null
)
list
.
add
(
image
);
}
return
list
;
}
private
byte
[
]
getRawBytes
(
String
fileName
)
{
if
(
fileName
==
null
)
fileName
=
"nordic.jpg"
;
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
();
try
{
FileInputStream
in
=
new
FileInputStream
(
fileName
);
if
(
in
==
null
)
in
=
new
FileInputStream
(
"nordic.jpg"
);
byte
[
]
buffer
=
new
byte
[
2048
];
int
n
=
0
;
while
((
n
=
in
.
read
(
buffer
))
!=
-
1
)
out
.
write
(
buffer
,
0
,
n
);
// append to array
in
.
close
();
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
return
out
.
toByteArray
();
}
}
Most of the code consists of utility methods that read bytes from a file and
transform these into a Java Image
. This service can be published straightforwardly with
Endpoint
:
package
images
;
import
javax.xml.ws.Endpoint
;
public
class
SkiImagePublisherBase64
{
public
static
void
main
(
String
[
]
args
)
{
System
.
out
.
println
(
"URL: http://localhost:9876/ski"
);
Endpoint
.
publish
(
"http://localhost:9876/ski"
,
new
SkiImageService
());
}
}
Here is a Perl client against the service—a client that explicitly consumes the service WSDL before making a request:
#
!/
usr
/
bin
/
perl
-
w
use
SOAP:
:
Lite
+
trace
=>
'
debug
'
;
use
strict
;
my
$url
=
'
http:
//localhost:9876/ski?wsdl';
my
$service
=
SOAP:
:
Lite
->
service
(
$url
);
$service
->
getImage
(
"nordic"
),
"\n"
;
#
base64
string
There is nothing unusual in either the service or the client: a client request results in a SOAP response whose body is text, but in this case it is text that is potentially huge in size because of the base64 encoding. The XML Schema in the service WSDL points to the problem. Here is a slice:
<
xs:
complexType
name
=
"getImageResponse"
>
<
xs:
sequence
>
<
xs:
element
name
=
"return"
type
=
"xs:base64Binary"
minOccurs
=
"0"
></
xs:
element
>
![]()
</
xs:
sequence
>
</
xs:
complexType
>
Line 1 shows that the XML Schema type of the response message getImageResponse
is, indeed, xs:base64Binary
.
For dramatic effect, here is a slice of the more than 30K-byte response to a getImage
request:
<?
xml
version
=
"1.0"
?>
<
S:
Envelope
xmlns:
S
=
"http://schemas.xmlsoap.org/soap/envelope/"
>
<
S:
Body
>
<
ns2:
getImageResponse
xmlns:
ns2
=
"http://images/"
>
<
return
>
iVBORw0KGgoAAAANSUhEUgAAAHwAAABWCAIAAACCS2W5AABY
...</
return
>
![]()
</
ns2:
getImageResponse
>
</
S:
Body
>
</
S:
Envelope
>
The element tagged return
(line 1) contains the base64 encoding of the image. The image itself,
nordic.jpg, is just under 3K bytes—and the SOAP response is just over 30K bytes. In this
case, the data bloat is ten-fold.
To avoid the data bloat associated with base64 or equivalent encoding, the service can be revised to take advantage of MTOM optimizations. Here are the steps to the revision:
The SkiImageService
class can be annotated to
signal that MTOM is in play. The revision is line 1:
@WebService
(
wsdlLocation
=
"mtom.wsdl"
)
@BindingType
(
value
=
SOAPBinding
.
SOAP11HTTP_MTOM_BINDING
)
// optional
![]()
public
class
SkiImageService
{
There is also a SOAP 1.2 binding for MTOM.
The XML Schema for the service WSDL needs to be edited in two places, as indicated in lines 1 and 2:
<
xsd:
complexType
name
=
"getImagesResponse"
>
<
xsd:
sequence
>
<
xsd:
element
name
=
"return"
type
=
"xsd:base64Binary"
minOccurs
=
"0"
maxOccurs
=
"unbounded"
xmime:
expectedContentTypes
=
"application/octet-stream"
![]()
xmlns:
xmime
=
"http://www.w3.org/2005/05/xmlmime"
>
</
xsd:
element
>
</
xsd:
sequence
>
</
xsd:
complexType
>
...
<
xsd:
complexType
name
=
"getImageResponse"
>
<
xsd:
sequence
>
<
xsd:
element
name
=
"return"
type
=
"xsd:base64Binary"
minOccurs
=
"0"
xmime:
expectedContentTypes
=
"application/octet-stream"
![]()
xmlns:
xmime
=
"http://www.w3.org/2005/05/xmlmime"
>
...
The MIME type application/octet-stream
indicates that the images are to be sent from the
service to the client as a byte stream. For simplicity and for proof of concept, the revised XML
Schema can be inserted into the WSDL directly (line 1):
<
definitions
xmlns:
soap
=
"http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:
tns
=
"http://images/"
xmlns:
xsd
=
"http://www.w3.org/2001/XMLSchema"
xmlns
=
"http://schemas.xmlsoap.org/wsdl/"
targetNamespace
=
"http://images/"
name
=
"SkiImageServiceService"
>
<
types
>
<
xsd:
schema
xmlns:
tns
=
"http://images/"
![]()
xmlns:
xsd
=
"http://www.w3.org/2001/XMLSchema"
...
Assume that the revised WSDL file, with the XML Schema inserted into it, is mtom.wsdl.
The @WebService
annotation for the SkiImageService
needs to be parametrized with the
location of the revised WSDL (line 1):
@WebService
(
wsdlLocation
=
"mtom.wsdl"
)
![]()
@BindingType
(
value
=
SOAPBinding
.
SOAP11HTTP_MTOM_BINDING
)
// optional
public
class
SkiImageService
{
If Endpoint
is used to publish the revised service, then the publisher can be revised to
indicate MTOM optimization (line 1), although this step is optional. Here is the
revised SkiImagePublisherMTOM
in full:
package
images
;
import
javax.xml.ws.Endpoint
;
import
javax.xml.ws.soap.SOAPBinding
;
public
class
SkiImagePublisherMTOM
{
private
Endpoint
endpoint
;
public
static
void
main
(
String
[
]
args
)
{
SkiImagePublisherMTOM
me
=
new
SkiImagePublisherMTOM
();
me
.
createEndpoint
();
me
.
configureEndpoint
();
me
.
publish
();
}
private
void
createEndpoint
()
{
endpoint
=
Endpoint
.
create
(
new
SkiImageService
());
}
private
void
configureEndpoint
()
{
SOAPBinding
binding
=
(
SOAPBinding
)
endpoint
.
getBinding
();
binding
.
setMTOMEnabled
(
true
);
![]()
}
private
void
publish
()
{
int
port
=
9876
;
String
url
=
"http://localhost:"
+
port
+
"/ski"
;
endpoint
.
publish
(
url
);
System
.
out
.
println
(
url
);
}
}
The revised publisher shows that the Endpoint
API is rich and flexible.
With these changes in place, an efficient Java client against the revised MTOM-based SkiImageService
can be built upon the usual wsimport artifacts. The by now familiar command:
%
wsimport
-
p
clientMTOM
-
keep
http:
//localhost:9876/ski?wsdl
generates the support classes for the revised SkiImageClient
(see Example 5-12).
Example 5-12. A Java client against the MTOM-based SkiImageService
import
clientMTOM.SkiImageServiceService
;
import
clientMTOM.SkiImageService
;
import
java.util.List
;
import
javax.activation.DataHandler
;
public
class
SkiImageClient
{
public
static
void
main
(
String
[
]
args
)
{
SkiImageService
port
=
new
SkiImageServiceService
().
getSkiImageServicePort
();
DataHandler
image
=
port
.
getImage
(
"nordic"
);
![]()
dump
(
image
);
List
<
DataHandler
>
images
=
port
.
getImages
();
![]()
for
(
DataHandler
dh
:
images
)
dump
(
dh
);
}
private
static
void
dump
(
DataHandler
dh
)
{
try
{
System
.
out
.
println
(
"MIME type: "
+
dh
.
getContentType
());
![]()
System
.
out
.
println
(
"Content: "
+
dh
.
getContent
());
![]()
}
catch
(
Exception
e
)
{
throw
new
RuntimeException
(
e
);
}
}
}
The Java DataHandler
type (lines 1 and 2) binds to the type application/octet-stream
. Each
image from the service is a DataHandler
instance whose properties (for instance, the
contentType
and content
properties shown in lines 3 and 4) are accessible with the familiar get-methods.
The output from a sample client run is:
MIME
type:
application
/
octet
-
stream
Content:
java
.
io
.
ByteArrayInputStream
@
3
f3e10ce
...
MIME
type:
application
/
octet
-
stream
Content:
java
.
io
.
ByteArrayInputStream
@ca753f7
The ByteArrayInputSteam
instances contain the bytes sent from the MTOM-enabled SkiImageService
. Although
the transmission is relatively efficient, the client now must deal with these bytes in some application-appropriate
way—for instance, by reconstructing the JPG images from the bytes.
In JAX-WS a client, too, can use MTOM to send media attachments to a service. Here is a revision of the
SkiImageClient
that shows the setup (lines 1 through 3):
SkiImageService
port
=
new
SkiImageServiceService
().
getSkiImageServicePort
();
BindingProvider
bp
=
(
BindingProvider
)
port
;
![]()
SOAPBinding
binding
=
(
SOAPBinding
)
bp
.
getBinding
();
![]()
binding
.
setMTOMEnabled
(
true
);
SOAP-based web services are at their best when dealing with text payloads, as the SOAP infrastructure
then assumes the burden of converting between native language types (for instance, a Java List<Prediction>
),
on the one side, and XML Schema types, on the other side. This advantage goes away once the SOAP payloads
are binary, regardless of whether the payload is base64 encoded text in the SOAP body or
SOAP attachments. Can SOAP-based services handle binary payloads? The answer is a firm yes, but this
answer invites the further question of whether SOAP-based services are ideally suited for binary payloads.
The answer to this second question is no.