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
