After passing the BasicAuthenticator
instance to HttpContext
's Authenticator setAuthenticator(Authenticator auth)
method, Endpoint
's Endpoint create(Object implementor)
method is called to create an Endpoint
instance with the specified UCImpl
instance as implementor's argument. This method's void publish(Object serverContext)
method is then called with the previous context, and the HttpServer
instance is started.
If you were to run UCPublisher
and UCClient
(see Part 2 for instructions), you would observe the same output shown in Part 2. However, if you modified UCClient
's credentials, you would observe a thrown exception in regard to not being able to access the WSDL when Service service = Service.create(url, qname);
attempts to execute; the WSDL isn't accessible because authentication has failed.
RESTful web services and attachments
RESTful Web services that implement Provider<Source>
cannot return arbitrary MIME-typed data (e.g., a JPEG image). They can only return XML messages with no attachments. If you want to return an attachment (such as an image file), your Web service class must implement the Provider<DataSource>
interface; the javax.activation.DataSource
interface provides the JavaBeans Activation Framework with an abstraction of an arbitrary collection of data.
Listing 8 presents an Image Publisher RESTful Web service that demonstrations how you could use DataSource
with two other javax.activation
package types to return a JPEG image to a client.
Listing 8. Returning a JPEG image in response to a GET request
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.activation.MimetypesFileTypeMap;
import javax.annotation.Resource;
import javax.xml.ws.BindingType;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Provider;
import javax.xml.ws.ServiceMode;
import javax.xml.ws.WebServiceContext;
import javax.xml.ws.WebServiceProvider;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;
import javax.xml.ws.http.HTTPException;
@WebServiceProvider
@ServiceMode(value = javax.xml.ws.Service.Mode.MESSAGE)
@BindingType(value = HTTPBinding.HTTP_BINDING)
public final class ImagePublisher implements Provider<DataSource>
{
@Resource
private WebServiceContext wsContext;
@Override
public DataSource invoke(DataSource request)
{
if (wsContext == null)
throw new RuntimeException("dependency injection failed on wsContext");
MessageContext msgContext = wsContext.getMessageContext();
switch ((String) msgContext.get(MessageContext.HTTP_REQUEST_METHOD))
{
case "GET" : return doGet();
default : throw new HTTPException(405);
}
}
private DataSource doGet()
{
FileDataSource fds = new FileDataSource("balstone.jpg");
MimetypesFileTypeMap mtftm = new MimetypesFileTypeMap();
mtftm.addMimeTypes("image/jpeg jpg");
fds.setFileTypeMap(mtftm);
System.out.println(fds.getContentType());
return fds;
}
public static void main(String[] args)
{
Endpoint.publish("http://localhost:9902/Image", new ImagePublisher());
}
}
Listing 8's ImagePublisher
class describes a simple RESTful Web service whose invoke()
method honors only the HTTP GET verb. Its doGet()
method responds to a GET request by returning the contents of the balstone.jpg
image file to the client.
doGet()
first instantiates the javax.activation.FileDataSource
class, which implements DataSource
, and which encapsulates a file to be returned as an attachment. doGet()
passes the name of this file to the FileDataSource(String name)
constructor.
doGet()
next instantiates the javax.activation.MimetypesFileTypeMap
class so that it can associate a MIME type with the JPEG file based on its jpg
file extension. This mapping is performed by invoking MimetypesFileTypeMap
's void addMimeTypes(String mime_types)
method, passing "image/jpeg jpg"
as the argument (image/jpeg
is the MIME type and jpg
is the file extension).
Continuing, doGet()
invokes FileDataSource
's void setFileTypeMap(FileTypeMap map)
method to associate the MimetypesFileTypeMap
instance with the FileDataSource
instance.
After invoking FileDataSource
's String getContentType()
method to return the MIME type of the file and outputting its return value, doGet()
returns the FileDataSource
object to invoke()
, which returns this object to the JAX-WS runtime.
Building and running ImagePublisher
Execute the following command to compile Listing 8:
javac --add-modules java.xml.ws ImagePublisher.java
Remove --add-modules java.xml.ws
when not compiling under Java SE 9.
Execute the following command to run ImagePublisher.class
:
java --add-modules java.xml.ws ImagePublisher
Start a Web browser and point it to http://localhost:9902/Image
. You should observe an image as shown in Figure 3.
Figure 3. Balanced stone at Arches National Park in eastern Utah
Providers and dispatch clients
This series presents high-level and low-level approaches to working with JAX-WS. The high-level approach requires you to work with SEIs and SIBs; it simplifies and hides the details of converting between Java method invocations and their corresponding SOAP-based XML messages. The low-level approach lets you work directly with XML messages, and must be followed to implement a RESTful Web service.
While discussing how to implement a RESTful Web service with JAX-WS (see Part 3), I introduced you to this API's Provider<T>
interface, whose invoke()
method is called by a client to receive and process a request, and to return a response. I then demonstrated how a client communicates with a provider by using the java.net.HttpURLConnection
class. Behind the scenes, the JAX-WS runtime takes the information received from the URL connection and creates the proper object to pass to invoke()
. It also takes the object returned from invoke()
and makes its contents available to the client via the URL connection's output stream.
JAX-WS also offers the javax.xml.ws.Dispatch<T>
interface as a client-side companion to Provider
. A client uses Dispatch
to construct messages or message payloads as XML, and is known as a dispatch client. As with Provider
, Dispatch
offers a T invoke(T)
method. Dispatch clients call this method to send messages synchronously to providers, and to obtain provider responses from this method's return value.
A dispatch client obtains an object whose class implements Dispatch<T>
by invoking one of Service
's createDispatch()
methods. For example, Dispatch<T> createDispatch(QName portName, Class<T> type, Service.Mode mode)
returns a Dispatch
instance for communicating with the Web service through the port identified by portName
, using the specified Source
, SOAPMessage
, or DataSource
counterpart to the actual type argument passed to Provider<T>
, and via the service mode (message or payload) passed to mode
.
After the Dispatch
instance has been obtained, a dispatch client will create an object conforming to the actual type argument passed to T
, and pass this instance to the Web service provider in a call to Dispatch
's invoke()
method. To understand the interplay between a dispatch client and a provider, consider a client that invokes Dispatch<Source>
's invoke()
method with an XML document made available via the Source
argument. The following sequence occurs:
- The provider's JAX-WS runtime dispatches the client request to
Provider<Source>
'sinvoke()
method. - The provider transforms the
Source
instance into an appropriatejavax.xml.transform.Result
instance (such as a DOM tree), processes thisResult
instance in some manner, and returns aSource
instance containing XML content to JAX-WS, which transmits the content toDispatch
'sinvoke()
method. Dispatch
'sinvoke()
method returns anotherSource
instance containing the XML content, which the dispatch client transforms into an appropriateResult
instance for processing.
Listing 9 demonstrates this interplay by providing an alternate version of the doGet()
method that appears in Part 3's LibraryClient
application. Instead of working with HttpURLConnection
, the alternate doGet()
method works with Service
and Dispatch
.
Listing 9. Revised LibraryClient
application's doGet()
method as a dispatch client
static void doGet(String isbn) throws Exception
{
Service service = Service.create(new QName(""));
String endpoint = "http://localhost:9902/library";
service.addPort(new QName(""), HTTPBinding.HTTP_BINDING, endpoint);
Dispatch<Source> dispatch;
dispatch = service.createDispatch(new QName(""), Source.class,
Service.Mode.MESSAGE);
Map<String, Object> reqContext = dispatch.getRequestContext();
reqContext.put(MessageContext.HTTP_REQUEST_METHOD, "GET");
if (isbn != null)
reqContext.put(MessageContext.QUERY_STRING, "isbn=" + isbn);
Source result;
try
{
result = dispatch.invoke(null);
}
catch (Exception e)
{
System.err.println(e);
return;
}
try
{
DOMResult dom = new DOMResult();
Transformer t = TransformerFactory.newInstance().newTransformer();
t.transform(result, dom);
XPathFactory xpf = XPathFactory.newInstance();
XPath xp = xpf.newXPath();
if (isbn == null)
{
NodeList isbns = (NodeList) xp.evaluate("/isbns/isbn/text()",
dom.getNode(),
XPathConstants.NODESET);
for (int i = 0; i < isbns.getLength(); i++)
System.out.println(isbns.item(i).getNodeValue());
}
else
{
NodeList books = (NodeList) xp.evaluate("/book", dom.getNode(),
XPathConstants.NODESET);
isbn = xp.evaluate("@isbn", books.item(0));
String pubYear = xp.evaluate("@pubyear", books.item(0));
String title = xp.evaluate("title", books.item(0)).trim();
String publisher = xp.evaluate("publisher", books.item(0)).trim();
NodeList authors = (NodeList) xp.evaluate("author", books.item(0),
XPathConstants.NODESET);
System.out.println("Title: " + title);
for (int i = 0; i < authors.getLength(); i++)
System.out.println("Author: " + authors.item(i).getFirstChild()
.getNodeValue().trim());
System.out.println("ISBN: " + isbn);
System.out.println("Publication Year: " + pubYear);
System.out.println("Publisher: " + publisher);
}
}
catch (TransformerException e)
{
System.err.println(e);
}
catch (XPathExpressionException xpee)
{
System.err.println(xpee);
}
System.out.println();
}
This method first invokes Service
's Service create(QName serviceName)
method to create a Service
instance that provides a client view of a Web service. In contrast to a Service
instance created from a WSDL file, where the qualified name of the service implementation class and other information is known to the Service
instance, a Service
instance created by a dispatch client doesn't need to have knowledge of the service when created; the information will be provided to this instance shortly. As a result, a QName
instance with an empty qualified name can be passed to create()
.
A Dispatch<T>
instance must be bound to a specific port and endpoint before use. As a result, doGet()
next invokes Service
's void addPort(QName portName, String bindingId, String endpointAddress)
method to create a new port for the service. (Ports created with this method contain no WSDL port type information and can be used only for creating Dispatch
instances.) The QName
argument passed to portName
can contain an empty qualified name. However, an appropriate binding must be specified via a String
-based binding identifier. This example specifies HTTPBinding.HTTP_BINDING
because we are communicating with a RESTful Web service via HTTP. Also, the target service's endpoint address must be specified as a URI, which happens to be http://localhost:9902/library
in this example.
After adding a port to the Service
object, doGet()
invokes createDispatch()
as explained earlier. Once again, a QName
object with an empty qualified name is passed because there is no WSDL to indicate a port name.
The returned Dispatch<Source>
object's Map<String,Object> getRequestContext()
method (which Dispatch
inherits from its BindingProvider
superinterface) is called to obtain the context that's used to initialize the message context for request messages. doGet()
inserts the request method verb (GET) and query string (isbn=isbn
) into this map, which will be made available to the provider.