JBoss EAP 6.1, jax-ws, cxf и русские символы в тегах XML
Чрезвычайно распространены веб сервисы в государственных структурах.
Это связано с довольно простой технологией, которая не требует длительного обучения программиста навыкам. Что и хорошо.
В результате, тем счастливчикам, кто подключаются к таким сервисам, остается только посочувствовать. И мы относимся к их числу.
Вот такой XML может прийти к нам с подобного сервиса:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <документ ДомАдрФЛ="Санкт-Петербург, Большой проспект Васильевского острова, 395-1" ПасНомФЛ="123456" ПасСерФЛ="09 08" ИннФЛ="7093291811" ОГРНЮЛ="70984700000213" МесПадТунМет="Нижняя Тунгусска, 34 километра левее третьей сосны." ДокИд="1234567"/>
Не трудно заметить, что названия объектов и полей на русском языке. Это очень удобно.
ДомАдрФЛ это конечно же домашний адрес физического лица,
ПасНомФЛ — номер паспорта физического лица,
ПасСерФЛ это серия паспорта физического лица,
ИннФЛ — ИНН физического лица,
ОГРНЮЛ — ОГРН физического лица,
МесПадТунМет это место падения Тунгусского метеорита,
ну и куде же без ДокИд — id документа.
Все ясно и понятно. Если нет, то изучайте предметную область — вот наш ответ.
Как известно имплементация apache cxf ws в нашей версии jboss не умеет корректно получать подобные сообщения.
То есть сообщение приходит в ISO кодировке и понятно, что анмаршалить принятое в объект не удастся.
Для того, чтобы исправить ситуацию, надо помочь cxf и вручную перекодировать сообщение.
Ни для кого не секрет, что сделать это можно при помощи интерсептора.
Примерно вот такого:
package ru.microgames.ws.interceptor; import org.apache.commons.io.IOUtils; import org.apache.cxf.binding.soap.interceptor.ReadHeadersInterceptor; import org.apache.cxf.interceptor.Fault; import org.apache.cxf.message.Message; import org.apache.cxf.phase.AbstractPhaseInterceptor; import org.apache.cxf.phase.Phase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; public class MessageConverterReceiveInterceptor extends AbstractPhaseInterceptor { private static final Logger log = LoggerFactory.getLogger(MessageConverterReceiveInterceptor.class); public MessageConverterReceiveInterceptor() { super(Phase.RECEIVE); log.info("MessageConverterReceiveInterceptor init."); addBefore(ReadHeadersInterceptor.class.getName()); } @Override public void handleMessage(Message message) throws Fault { log.info("MessageConverterReceiveInterceptor encoding: {}", message.get(Message.ENCODING)); message.put(Message.ENCODING, "UTF-8"); try { InputStream is = message.getContent(InputStream.class); String receivedMessage = IOUtils.toString(is, "UTF-8"); IOUtils.closeQuietly(is); log.info("MessageConverterReceiveInterceptor message: {}", receivedMessage); is = IOUtils.toInputStream(receivedMessage, "UTF-8"); message.setContent(InputStream.class, is); IOUtils.closeQuietly(is); } catch (IOException ioe) { log.error("Unable to perform change. {}", ioe); throw new RuntimeException(ioe); } } }
Полная документация apache cxf интерсепторов находится тут http://cxf.apache.org/docs/interceptors.html
Нам надо в самом начале перехватывать сообщение, поэтому напишем в коде такое в конструкторе:
super(Phase.RECEIVE);
Осталось подключить наш интерсептор к сервису. Сделать это можно вот таким образом.
import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; import ru.microgames.ws.interceptor.MessageConverterReceiveInterceptor; ... private IntegrationService service; Integration port = service.getPort(); Client client = ClientProxy.getClient(port); client.getInInterceptors().add(new MessageConverterReceiveInterceptor());
Теперь в лог будет выводиться принятое сообщение и даже можно будет на него посмотреть в дебаге.
И не надо бояться русских букв. Другого надо бояться.
Это один из способов перекодирования сообщения, для того, чтобы использовать русские символы в названиях полей и объектов.
Кроме того, пример практического использования интерсепторов. Наиболее часто их используют для логирования сообщений.
И да, чтобы поиграться — сам Java объект:
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlType; @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "\u201e\u00ae\u0404\u0433\u00ac\u0490\u00ad\u0432") public class Документ { @XmlAttribute(name = "\u201e\u00ae\u00ac\u0402\u00a4\u0430\u201d\u2039") protected String ДомАдрФЛ; @XmlAttribute(name = "\u040f\u00a0\u0431\u040c\u00ae\u00ac\u201d\u2039", required = true) protected String ПасНомФЛ; @XmlAttribute(name = "\u040f\u00a0\u0431\u2018\u0490\u0430\u201d\u2039", required = true) protected String ПасСерФЛ; @XmlAttribute(name = "\u20ac\u00ad\u00ad\u201d\u2039") protected String ИннФЛ; @XmlAttribute(name = "\u040b\u0453\u0452\u040c\u045b\u2039") protected String ОГРНЮЛ; @XmlAttribute(name = "\u040a\u0490\u0431\u040f\u00a0\u00a4\u2019\u0433\u00ad\u040a\u0490\u0432") protected String МесПадТунМет; @XmlAttribute(name = "\u201e\u00ae\u0404\u20ac\u00a4") protected Long ДокИд; public String getДомАдрФЛ() { return ДомАдрФЛ; } public void setДомАдрФЛ(String домАдрФЛ) { ДомАдрФЛ = домАдрФЛ; } public String getПасНомФЛ() { return ПасНомФЛ; } public void setПасНомФЛ(String пасНомФЛ) { ПасНомФЛ = пасНомФЛ; } public String getПасСерФЛ() { return ПасСерФЛ; } public void setПасСерФЛ(String пасСерФЛ) { ПасСерФЛ = пасСерФЛ; } public String getИннФЛ() { return ИннФЛ; } public void setИннФЛ(String иннФЛ) { ИннФЛ = иннФЛ; } public String getОГРНЮЛ() { return ОГРНЮЛ; } public void setОГРНЮЛ(String ОГРНЮЛ) { this.ОГРНЮЛ = ОГРНЮЛ; } public String getМесПадТунМет() { return МесПадТунМет; } public void setМесПадТунМет(String месПадТунМет) { МесПадТунМет = месПадТунМет; } public Long getДокИд() { return ДокИд; } public void setДокИд(Long докИд) { ДокИд = докИд; } }
И тестовый класс для маршаллинга.
import javax.xml.bind.JAXB; import java.io.StringWriter; ... Документ d = new Документ(); d.setДокИд(1234567L); d.setДомАдрФЛ("Санкт-Петербург, Большой проспект Васильевского острова, 395-1"); d.setИннФЛ("7093291811"); d.setМесПадТунМет("Нижняя Тунгусска, 34 километра левее третьей сосны."); d.setОГРНЮЛ("70984700000213"); d.setПасСерФЛ("09 08"); d.setПасНомФЛ("123456"); StringWriter sw = new StringWriter(); JAXB.marshal(d, sw); System.out.println("Документ: "+sw.toString());
Александр Смелков
Санкт-Петербург Зима 2016