Category Archive: Веб-сервисы

Встраивание атрибута Id в тег Body SOAP-запроса

С некоторой версии Java (примерно с 7u25 см. http://stackoverflow.com/questions/17331187/xml-dig-sig-error-after-upgrade-to-java7u25) начались проблемы со встаиванием атрибута Id в тег Body SOAP-запроса. Такое встраивание необходимо с целью подписания запроса ЭЦП.
Пока я встретил две ошибки в зависимости каким путем идет встраивание атрибута Id:

  1. com.sun.org.apache.xml.internal.security.utils.resolver.implementations.ResolverFragment
  2. org.apache.xml.security.utils.resolver.ResourceResolverException: Cannot resolve element with ID …

Чтобы решить проблему нужно специальным образом пометить атрибут Id как ID (простите за тавтологию)

Первая ошибка возникает в коде такого вида:

Attr attr = doc.createAttribute("id");
attr.setValue("body");
body.getAttributes().setNamedItem(attr);

Подкорректированная версия:

Attr attr = doc.createAttribute("id");
attr.setValue("body");
body.getAttributes().setNamedItem(attr);
((Element) body).setIdAttributeNode(attr, true);//fix

Вторая ошибка возникает в коде вида:

SOAPBody body = env.getBody();
body.addAttribute(new QName("Id"), id);

Подкорректированная версия:

SOAPBody body = env.getBody();
body.addAttribute(new QName("Id"), id);
body.setIdAttribute("Id", true);//fix

Если помог отпишитесь 🙂

Набор методов для маршалинга и анмаршалинга

При создании и обращении к веб-сервисам в большинстве случаем мы можем забыть и не знать о маршалинге и анмаршалинге (демаршалинге). Но бывают интеграции, где каким-то текстовым полем передается XML-ка. Вот тут нужно с одной стороны уметь делать маршалинг, а с другой анмаршалинг. Предлагаю набор методов для проведения данных операций:

import javax.xml.bind.*;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;
 
/**
 * Набор методов для маршалинга и анмаршалинга
 */
public class XmlUtils {
 
	/**
	 * Маршалинг в строку
	 * @param t объект
	 * @param <T> тип объекта
	 * @return XML в виде строки
	 * @throws JAXBException
	 */
	public static<T> String marshalRoot(T t) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(t.getClass());
		Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
		StringWriter sw = new StringWriter();
		marshaller.marshal(t,sw);
		return sw.toString();
	}
 
	/**
	 * Маршалинг в файл
	 * @param t объект
	 * @param file файл
	 */
	public static<T> void marshalRoot(T t,File file){
		marshal(t,file,false);
	}
 
	/**
	 * Маршалинг в файл с возможностью вывода в консоль
	 * @param t объект
	 * @param file файл
	 * @param isAddToLog делать вывод в консоль или нет
	 */
	public static<T> void marshal(T t,File file,boolean isAddToLog){
		String filePath = file.getAbsolutePath();
		try {
			JAXBContext jaxbContext = JAXBContext.newInstance(t.getClass());
			Marshaller marshaller = jaxbContext.createMarshaller();
			marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,true);
			marshaller.marshal(t,file);
			if(isAddToLog){
				marshaller.marshal(t,System.out);
			}
		} catch (JAXBException e) {
			System.out.println("Не удалось сбросить xml в файл: " + filePath);
			e.printStackTrace();
		}
		System.out.println("Xml saved to: " + filePath);
	}
 
	/**
	 * Анмаршалинг (Демаршалинг) XML в объект
	 * @param xml входной XML
	 * @param _class класс объекта, который ходит получить
	 * @return объект
	 * @throws JAXBException
	 */
	@SuppressWarnings("unchecked")
	public static <T> T unmarshall(String xml,Class<T> _class) throws JAXBException {
		JAXBContext context = JAXBContext.newInstance(_class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		JAXBElement<T> element = (JAXBElement<T>)unmarshaller.unmarshal(new StringReader(xml));
		return element.getValue();
	}
 
	/**
	 * Анмаршалинг XML root-элемента (@XmlRootElement) в объект
	 * @param xml входной XML
	 * @param _class класс объекта, который ходит получить
	 * @return объект
	 * @throws JAXBException
	 */
	@SuppressWarnings("unchecked")
	public static <T> T unmarshallRoot(String xml,Class<T> _class) throws JAXBException{
		JAXBContext context = JAXBContext.newInstance(_class);
		Unmarshaller unmarshaller = context.createUnmarshaller();
		return (T)unmarshaller.unmarshal(new StringReader(xml));
	}
 
 
}

Генерация Java-классов Web-сервиса по WSDL

Допустим, у вас есть WSDL и вы хотите написать либо сам веб-сервис, либо клиента для сервиса. Для этого по WSDL нужно сгенерировать Java-классы с помощью утилиты wsimport, входящую в состав JDK.
Стоит отметить, что сгенерированный код может использовать и на сервере, и на клиенте.
Создайте новую папку и положите в нее вашу WSDL и всё, что к ней относится (это может быть другие WSDL или XSD). Увидеть все, что импортирует ваша основная WSDL можно в теге import. Например:

<xsd:import namespace="http://www.kesh.kz/blog/v1" 
schemaLocation="http://123.45.678.90:8080/kesh/example.xsd"/>

В данном примере следует заменить интернет адрес XSD на локальный:

<xsd:import namespace="http://www.kesh.kz/blog/v1" 
schemaLocation="example.xsd"/>

Разумеется, файл example.xsd должен лежать c WSDL в одной директории.
Теперь напишем скрипт для генерирования классов. Рассмотрим сперва Windows-версию скрипта, а потом Linux-версию.

Скрипт для Windows
Создайте файл w-gen.bat в любом текстовом редакторе и скопируйте в него следующий код:

cls
set GEN_DIR=kz
rmdir %GEN_DIR% /s/q
"%JAVA_HOME%/bin/wsimport" -keep -Xnocompile -p kz.kesh.blog.v1 myBlog.wsdl
pause

cls — очистка консоли
GEN_DIR — директория пакета верхнего уровня
rmdir — полное удаление директории
wsimport — утилита для генерации Java-классов из WSDL
keep — сохраняет сгенерированные файлы
Xnocompile — не компилирует сгенерированные Java-файлы
kz.kesh.blog.v1 — имя пакета, в котором будет сгенерированые классы
myBlog.wsdl — наша WSDL
pause — пауза, чтобы консольное окно сразу не закрылось

Данный скрипт генерирует *.java файлы, но вы можете немного переработать скрипт и генерировать скопилированные файлы *.class. Так же вы можете доработать упаковку файлов и WSDL в jar. Например:

"%JAVA_HOME%/bin/jar" cf keskBlog.jar kz

jar — стандартная утилита из JDK для создания jar-файлов
cf — создает новый архив с указанным именем
keskBlog.jar — имя вашего jar-файла
kz — директория, которая упаковывается в jar, она соответствует названию верхнего пакета

Скрипт для Linux
Создайте файл w-gen.sh:

#!/bin/bash
GEN_DIR="kz"
rm -r $GEN_DIR
wsimport -keep -Xnocompile -p kz.kesh.blog.v1 myBlog.wsdl

Не забудьте дать скрипту права на выполенение:

chmod +x w-gen.sh

#!/bin/bash — обязательная строчка для bash-скриптов
GEN_DIR — директория пакета верхнего уровня
rm — рекурсивное удаление директории
wsimport — утилита для генерации Java-классов из WSDL
keep — сохраняет сгенерированные файлы
Xnocompile — не компилирует сгенерированные Java-файлы
kz.kesh.blog.v1 — имя пакета, в котором будет сгенерированы классы
myBlog.wsdl — наша WSDL