JBoss EAP 6.1, jax-ws, cxf и русские символы в тегах XML

В статье речь пойдет о применении русских символов в веб сервисах, которые работают на JBoss. Имеем JBoss EAP 6.1.0 и, соответственно, JBossWS-CXF.

Чрезвычайно распространены веб сервисы в государственных структурах.
Это связано с довольно простой технологией, которая не требует длительного обучения программиста навыкам. Что и хорошо.

В результате, тем счастливчикам, кто подключаются к таким сервисам, остается только посочувствовать. И мы относимся к их числу.

Вот такой 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