HandlerChain в java SOAP веб-сервисе. Применение цепочки handler’ов
Использование аннотации @HandlerChain при создании Java SOAP web-service.
Используемые технологии и библиотеки
- Apache CXF 3.1.6
- Spring MVC 4.3.0.Release
1. Описание задачи
Показать на примере использование цепочки обработчиков, которые задаются с помощью аннотации @HandlerChain и xml файла с описанием хендлеров.
2. Структура проекта
l;
Относительно начального проекта SOAP java веб сервис. Пример Hello World, здесь добавлен один класс HandlerValidator и один xml файл — handler-chain.xml. Именно они и являются основными для темы данной статьи.
Данная статья будет описывать серверную часть (сам веб-сервис). К ней доступна статья описывающая клиентскую часть для тестирования этого веб-сервиса и handler’а.
3. Конфигурация: pom.xml, application-context.xml
Изменений относительно hello world примера из статьи чуть выше в этих файлах нет.
4. @HandlerChain
Аннотация @HandlerChain (цепочка обработчиков) используется для указания SOAP веб-сервису списка классов-обработчиков. В общем случае эти классы используются для изменения входящих и исходящих сообщений SOAP веб-сервиса. Если вы знакомы с сервлетами или перехватчиками, то хендлеры являются аналогом Servlet Filters или CDI\EJB Interceptors.
В классе HelloSoap была добавлена аннотация @HandlerChain и параметр file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package ru.javastudy.ws.soap; import ru.javastudy.ws.model.Goods; import javax.jws.HandlerChain; import javax.jws.WebService; /** * Created for JavaStudy.ru on 10.06.2016. */ @WebService(endpointInterface = "ru.javastudy.ws.soap.WebserviceSEI", serviceName = "HelloSoap") @HandlerChain(file = "../../../../handler-chain.xml") public class HelloSoap implements WebserviceSEI { @Override public String testService() { return "Hello from SOAP Webservice!"; } @Override public String sayHelloTo(String text) { return "Hello to " + text; } @Override public Goods getGoods() { Goods goods = new Goods(); goods.setId(1); goods.setName("Some goods test name"); return goods; } } |
Обратите внимание как описан путь, чтобы xml файл был корректно найден в проекте.
4.1. handler-chain.xml
Наш веб-сервис HelloSoap ассоциируется с цепочкой обработчиков в указанном xml файле.
1 2 3 4 5 6 7 8 9 10 11 12 |
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <handler-chains xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/javaee_web_services_metadata_handler_2_0.xsd"> <handler-chain> <handler> <handler-class>ru.javastudy.ws.handlers.HandlerValidator</handler-class> </handler> </handler-chain> </handler-chains> |
В примере используется только один handler и для добавления новых классов обработчиков нужно просто добавить описание в новом теге <handler>.
4.2. HandlerValidator implements SOAPHandler
Handler должен реализовывать интерфейс SOAPHandler.
HandlerValidator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
package ru.javastudy.ws.handlers; import javax.xml.namespace.QName; import javax.xml.soap.*; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import javax.xml.ws.soap.SOAPFaultException; import java.io.IOException; import java.util.Iterator; import java.util.Set; /** * Created for JavaStudy.ru on 12.06.2016. */ public class HandlerValidator implements SOAPHandler<SOAPMessageContext> { @Override public boolean handleMessage(SOAPMessageContext context) { System.out.println("HandlerValidator on server side"); System.out.println("Server : handleMessage()......"); Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); //for response message only, true for outbound messages, false for inbound if(!isRequest){ try{ SOAPMessage soapMsg = context.getMessage(); SOAPEnvelope soapEnv = soapMsg.getSOAPPart().getEnvelope(); SOAPHeader soapHeader = soapEnv.getHeader(); //if no header, add one if (soapHeader == null){ soapHeader = soapEnv.addHeader(); //throw exception generateSOAPErrMessage(soapMsg, "No SOAP header."); } //Get client mac address from SOAP header Iterator it = soapHeader.extractHeaderElements(SOAPConstants.URI_SOAP_ACTOR_NEXT); //if no header block for next actor found? throw exception if (it == null || !it.hasNext()){ generateSOAPErrMessage(soapMsg, "No header block for next actor."); } //if no mac address found > throw exception assert it != null; Node passNode = (Node) it.next(); String password = (passNode == null) ? null : passNode.getValue(); if (password == null){ generateSOAPErrMessage(soapMsg, "Try Der Parol"); } //if mac address is not match, throw exception if("For the Horde.".equals(password)){ generateSOAPErrMessage(soapMsg, "The sky is falling"); } else if ("Life for Ner'Zhul".equals(password)) { generateSOAPErrMessage(soapMsg, "For the Lich King!"); } //tracking soapMsg.writeTo(System.out); } catch(SOAPException | IOException e){ System.err.println(e); } } //continue other handler chain return true; } @Override public Set<QName> getHeaders() { return null; } @Override public boolean handleFault(SOAPMessageContext context) { return false; } @Override public void close(MessageContext context) { } private void generateSOAPErrMessage(SOAPMessage msg, String reason) { try { SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody(); SOAPFault soapFault = soapBody.addFault(); soapFault.setFaultString(reason); throw new SOAPFaultException(soapFault); } catch(SOAPException ignored) { } } } |
Данный обработчик принимает на вход контекст SOAP сообщения и использует его данные для преобразования исходящего сообщения.
Вначале мы проверяем входящие или исходящие это сообщение.
1 2 3 4 5 |
Boolean isRequest = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); //for response message only, true for outbound messages, false for inbound if(!isRequest){ .... |
Мы будем преобразовывать только исходящее (response) сообщение. Вначале мы получаем заголовок сообщения и если он отсутствует, то выводим сообщение с ошибкой. Далее получаем итератор элементов в заголовке. Каждый элемент это node, которую можно изменить или прочитать. Для нашего примера мы получаем значения элемента и далее сравниваем его с заранее заданным значением.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//if no password found > throw exception assert it != null; Node passNode = (Node) it.next(); String password = (passNode == null) ? null : passNode.getValue(); if (password == null){ generateSOAPErrMessage(soapMsg, "Try Der Parol"); } //if password is not match, throw exception if("For the Horde.".equals(password)){ generateSOAPErrMessage(soapMsg, "The sky is falling"); } else if ("Life for Ner'Zhul".equals(password)) { generateSOAPErrMessage(soapMsg, "For the Lich King!"); } |
Сразу поясню, что «пароль» мы будем записывать на клиенте. Эта информация известна и поэтому проверочный код достаточно сильно упрощен. В реальных случаях логика метода handleMessage() может быть намного сложнее.
Для тестирования работоспособности нашей цепочки из одного обработчика напишем клиентскую часть. Ее описание доступно по ссылке — HandlerChain в java SOAP веб-сервисе. Клиентская часть