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 веб-сервисе. Клиентская часть



