В прошлом посте мы начали рассказывать о разработке на платформе WSO2 и сделали REST API-сервис. Сегодня мы продолжаем тему: в этом посте поделюсь с вами тем, как мы в WSO2 создаем SOAP-сервисы. В этом посте я делаю акцент на различиях, поэтому на случай каких-то общих вопросов можете параллельно открыть пост про REST API.
В WSO2 Integration Studio создаем новый проект (File – New – Integration Project). Назовем его, например, Rosstat Service. Не забываем создать RegistryResources, поскольку SOAP-сервис в любом случае предусматривает WSDL/XSD, а их нужно где-то хранить
Добавляем в каждый из отмеченных каталогов пустой файл с именем ".gitkeep" для контроля версий:
Далее настраиваем остальные параметры в зависимости от проекта. Все ссылки репозитория WSO2 Nexus во всех файлах "pom.xml" меняем на ваши собственные. У нас, соответственно, https://maven.wso2.org/nexus/content/groups/wso2-public/ меняется на https://nexus.gts.rus.socgen/repository/maven-wso2-public/.
Создаем шаблоны
Создадим шаблон для вызова внешнего сервиса CallAPI.
В типе шаблона нужно выбрать Sequence Template. С точки зрения кода шаблон будет таким же, как и при создании REST API-сервиса, поскольку мы принимаем SOAP, а вызываем REST:
<template name="MOEX_callAPI_Template" xmlns="http://ws.apache.org/ns/synapse">
<parameter defaultValue="" isMandatory="false" name="setCallEndpointName"/>
<parameter defaultValue="" isMandatory="false" name="setEndpointURL"/>
<parameter defaultValue="" isMandatory="false" name="setEndpointURL_Suffix"/>
<parameter defaultValue="" isMandatory="false" name="setQueryParams"/>
<sequence>
<property expression="get-property('file', $func:setEndpointURL)" name="ENV_ENDPOINT_URL" scope="default" type="STRING"/>
<filter regex="true" source="boolean($ctx:ENV_ENDPOINT_URL) and string-length($ctx:ENV_ENDPOINT_URL) > 0">
<then/>
<else>
<property expression="get-property('env', $func:setEndpointURL)" name="ENV_ENDPOINT_URL" scope="default" type="STRING"/>
</else>
</filter>
<switch source="boolean($func:setQueryParams) and string-length($func:setQueryParams) > 0">
<case regex="true">
<property expression="fn:concat($ctx:ENV_ENDPOINT_URL, $func:setEndpointURL_Suffix, '?', $func:setQueryParams)" name="uri.var.endpointURL_FullAddress" scope="default" type="STRING"/>
</case>
<default>
<property expression="fn:concat($ctx:ENV_ENDPOINT_URL, $func:setEndpointURL_Suffix)" name="uri.var.endpointURL_FullAddress" scope="default" type="STRING"/>
</default>
</switch>
<class description="OutReqLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT">
<property name="logLevel" value="DEBUG"/>
<property name="ReqType" value="OutRequest"/>
<property expression="fn:concat('sending request to endpointURL_FullAddress=', $ctx:uri.var.endpointURL_FullAddress)" name="Payload"/>
</class>
<call>
<endpoint key-expression="$func:setCallEndpointName"/>
</call>
</sequence>
</template>
Теперь создаем шаблон для обработки ошибок SOAPFault. Здесь нужно обратить внимание, какая версия протокола SOAP используется, 1.1 или 1.2. От этого будут зависеть некоторые параметры формирования ошибки, в частности, различаться ContentType. Вот код обработчика для SOAP 1.1:
<template name="Rosstat_SOAPFault_Template" xmlns="http://ws.apache.org/ns/synapse">
<parameter defaultValue="" isMandatory="false" name="MessageID"/>
<parameter defaultValue="" isMandatory="false" name="ErrorText"/>
<parameter defaultValue="" isMandatory="false" name="Exception"/>
<sequence>
<payloadFactory description="ExceptionPayload" media-type="xml">
<format>
<m:Exception xmlns:m="http://wso2.rosbank.ru/types/Fault">
<msgID xmlns="">$1</msgID>
<text xmlns="">$2</text>
<exception xmlns="">$3</exception>
</m:Exception>
</format>
<args>
<arg evaluator="xml" expression="$func:MessageID"/>
<arg evaluator="xml" expression="$func:ErrorText"/>
<arg evaluator="xml" expression="$func:Exception"/>
</args>
</payloadFactory>
<enrich description="body to property">
<source clone="true" type="body"/>
<target property="MESSAGE_PAYLOAD" type="property"/>
</enrich>
<makefault description="SoapFault" version="soap11">
<code value="soap11Env:Server" xmlns:soap11Env="http://schemas.xmlsoap.org/soap/envelope/"/>
<reason value="Error message processing"/>
<detail expression="get-property('MESSAGE_PAYLOAD')"/>
</makefault>
<propertyGroup description="removeHeaders">
<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
</propertyGroup>
<propertyGroup description="setHeaders">
<property name="HTTP_SC" scope="axis2" type="STRING" value="500"/>
<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="Internal Server Error"/>
<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/>
<property name="messageType" scope="axis2" type="STRING" value="text/xml"/>
<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/>
<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/>
<property name="RESPONSE" scope="default" type="STRING" value="true"/>
</propertyGroup>
</sequence>
</template>
При возникновении ошибки этот обработчик передает XML-сообщение с уникальным id, по которому можно будет легко ее найти в логах. Также не забудьте проставить свою версию SOAP для SOAPFault: description="SoapFault" version="soap11"
.
А вот код обработчика для SOAP 1.2:
<template name="Rosstat_SOAPFault_Template" xmlns="http://ws.apache.org/ns/synapse">
<parameter defaultValue="" isMandatory="false" name="MessageID"/>
<parameter defaultValue="" isMandatory="false" name="ErrorText"/>
<parameter defaultValue="" isMandatory="false" name="Exception"/>
<sequence>
<payloadFactory description="ExceptionPayload" media-type="xml">
<format>
<m:Exception xmlns:m="http://wso2.rosbank.ru/types/Fault">
<msgID xmlns="">$1</msgID>
<text xmlns="">$2</text>
<exception xmlns="">$3</exception>
</m:Exception>
</format>
<args>
<arg evaluator="xml" expression="$func:MessageID"/>
<arg evaluator="xml" expression="$func:ErrorText"/>
<arg evaluator="xml" expression="$func:Exception"/>
</args>
</payloadFactory>
<enrich description="body to property">
<source clone="true" type="body"/>
<target property="MESSAGE_PAYLOAD" type="property"/>
</enrich>
<makefault description="SoapFault" version="soap12">
<code value="soap12Env:Receiver" xmlns:soap12Env="http://www.w3.org/2003/05/soap-envelope"/>
<reason value="Error message processing"/>
<detail expression="get-property('MESSAGE_PAYLOAD')"/>
</makefault>
<propertyGroup description="removeHeaders">
<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
</propertyGroup>
<propertyGroup description="setHeaders">
<property name="HTTP_SC" scope="axis2" type="STRING" value="500"/>
<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="Internal Server Error"/>
<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap12"/>
<property name="messageType" scope="axis2" type="STRING" value="application/soap+xml"/>
<property name="ContentType" scope="axis2" type="STRING" value="application/soap+xml"/>
<property name="Content-Type" scope="transport" type="STRING" value="application/soap+xml;charset=UTF-8"/>
<property name="RESPONSE" scope="default" type="STRING" value="true"/>
</propertyGroup>
</sequence>
</template>
Создаем обработчики
Теперь создаем общие шаги — Sequence — для обработки входящих ошибок:
FaultSequence перехватывает исключения, которые могут произойти при обработке запроса, и вызывает шаблон SOAPFault, который мы только что сделали:
Создадим обработчик Inbound для приема входящих запросов:
<sequence name="Rosstat_InboundSequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<propertyGroup description="setProperty">
<property name="ENV_SERVICE_NAME" scope="default" type="STRING" value="RosstatService"/>
<property expression="$axis2:TransportInURL" name="ENV_REST_URL_POSTFIX" scope="default" type="STRING"/>
<property expression="fn:substring-after(get-property('MessageID'), 'uuid:')" name="ENV_MESSAGE_ID" scope="default" type="STRING"/>
<property expression="local-name(//soapenv:Envelope/soapenv:Body/*[1])" name="ENV_METHOD_NAME" scope="default" type="STRING" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"/>
<property expression="fn:substring-after($axis2:TransportInURL, '?')" name="ENV_QUERY_PARAMS" scope="default" type="STRING"/>
<property expression="string('')" name="ENV_EMPTY_STRING" scope="default" type="STRING"/>
<property expression="$axis2:REMOTE_ADDR" name="ENV_IP_ADDRESS" scope="default" type="STRING"/>
</propertyGroup>
<class description="InReqLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT">
<property name="logLevel" value="DEBUG"/>
<property name="ReqType" value="Begin"/>
<property name="Payload" value="Request accepted..."/>
</class>
</sequence>
В параметре ENV_SERVICE_NAME указываем название сервиса. Это же имя будет использоваться в LoggerService для записи логов в отдельный файл. В параметре "onError" здесь и далее в инструкции указываем ссылку на ранее созданный обработчик ошибок: onError="Rosstat_FaultSequence"
.
Здесь есть небольшие отличия от REST. Сгенирировав uuid, мы идем в SOAP Envelope – Body и оттуда получаем название метода. В протоколе SOAP название метода передается именно в теле.
Теперь сделаем Outbound — обработчик, который будет заносить в лог факт обработки запроса.
<sequence name="Rosstat_OutboundSequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<class description="InResLoggerTXT" name="ru.rosbank.mediator.LoggerMediatorTXT">
<property name="logLevel" value="DEBUG"/>
<property name="ReqType" value="End"/>
<property name="Payload" value="Request processed success"/>
</class>
</sequence>
И наконец, новый обработчик, которого не было в REST API. UnsupportedSequence используется для обработки ошибки, когда метод был объявлен в WSDL, но не был реализован (или мы сами не хотим его выставлять).
Здесь тоже используется шаблон SOAPFault. В сообщении ошибки мы выдаем «Unsupported service»:
<sequence name="Rosstat_UnsupportedService_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<class description="InReqLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="InRequest"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
<call-template description="SoapFault" target="Rosstat_SOAPFault_Template">
<with-param name="MessageID" value="{$ctx:ENV_MESSAGE_ID}"/>
<with-param name="ErrorText" value="Unsupported service"/>
<with-param name="Exception" value="{$ctx:ENV_EMPTY_STRING}"/>
</call-template>
<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="InResponse"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
</sequence>
Еще один обработчик, которого не было в REST API — обработчик конкретного метода (операции) GetOrganizationInfo. В REST API мы это делали на уровне API с помощью <resource>
, здесь это устроено иначе.
Логируем запрос, вызываем HTPP-сервис и логируем ответ:
<sequence name="Rosstat_GetOrganizationInfo_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<class description="InReqLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="InRequest"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
<sequence key="Rosstat_GetOrganizationInfo_HTTPRequest_Sequence"/>
<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="InResponse"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
</sequence>
Теперь нам нужно создать обработчик запросов.
Здесь мы удаляем все транспортные заголовки, превращаем SOAP в JSON, задаем формат из трех полей и ContentType, логируем запрос, вызываем CallTemplate и проверяем, что вернулся код 200. Если да, то мы вызываем sequence для обратной трансформации, если нет — логируем ошибку, вызываем шаблон SOAPfault:
<sequence name="Rosstat_GetOrganizationInfo_HTTPRequest_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<propertyGroup description="removeHeaders">
<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
</propertyGroup>
<payloadFactory description="MappingIn" media-type="json">
<format>{"okpo":"$1", "inn":"$2", "ogrn":"$3"}</format>
<args>
<arg evaluator="xml" expression="//p:okpo/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/>
<arg evaluator="xml" expression="//p:inn/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/>
<arg evaluator="xml" expression="//p:ogrn/text()" xmlns:p="http://ru.rosbank.servicemix/cd/client"/>
</args>
</payloadFactory>
<propertyGroup description="setHeader">
<property name="messageType" scope="axis2" type="STRING" value="application/json"/>
</propertyGroup>
<class description="OutReqLoggerJSON" name="ru.rosbank.mediator.LoggerMediatorJSON">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="OutRequest"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
<call-template description="callRosstat" onError="Rosstat_FaultSequence" target="Rosstat_callAPI_Template">
<with-param name="setCallEndpointName" value="Rosstat_GetOrganizationInfo_EP"/>
<with-param name="setEndpointURL" value="webServiceURL_GetRosstatOrganizationInfo"/>
<with-param name="setEndpointURL_Suffix" value=""/>
<with-param name="setQueryParams" value=""/>
</call-template>
<filter regex="200" source="$axis2:HTTP_SC">
<then>
<class description="OutResLoggerJSON" name="ru.rosbank.mediator.LoggerMediatorJSON">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="OutResponse"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
<sequence key="Rosstat_GetOrganizationInfo_MappingOUT_Sequence"/>
</then>
<else>
<class description="OutResLoggerBINARY" name="ru.rosbank.mediator.LoggerMediatorBINARY">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="OutResponse"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
<call-template description="SoapFault" target="Rosstat_SOAPFault_Template">
<with-param name="MessageID" value="{$ctx:ENV_MESSAGE_ID}"/>
<with-param name="ErrorText" value="GetOrganizationInfo returns non success response"/>
<with-param name="Exception" value="{fn:concat('Error: ', $axis2:HTTP_SC, ' - ', $axis2:HTTP_SC_DESC)}"/>
</call-template>
</else>
</filter>
</sequence>
Следующий шаг — MappingOUT — обратная трансформация из JSON в SOAP:
Здесь мы сохраняем в локальные переменные из JSON интересные нам id, type и name и генерируем payload для xml-ки SOAPResponse:
<sequence name="Rosstat_GetOrganizationInfo_MappingOUT_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<propertyGroup description="Group of properties that extract data from response">
<property expression="json-eval($.[0].id)" name="id" scope="default" type="STRING"/>
<property expression="json-eval($.[0].type)" name="type" scope="default" type="STRING"/>
<property expression="json-eval($.[0].name)" name="name" scope="default" type="STRING"/>
<!-- остальные параметры протокола обмена удалены, для упрощения чтения -->
</propertyGroup>
<payloadFactory description="SOAPResponse" media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<p:RosstatOrganizationInfoResponse xmlns:p="http://ru.rosbank.servicemix/cd/client">
<p:id>$1</p:id>
<p:type>$2</p:type>
<p:name>$3</p:name>
</p:RosstatOrganizationInfoResponse>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args>
<arg evaluator="xml" expression="get-property('id')"/>
<arg evaluator="xml" expression="get-property('type')"/>
<arg evaluator="xml" expression="get-property('name')"/>
</args>
</payloadFactory>
<propertyGroup description="removeHeaders">
<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
</propertyGroup>
<propertyGroup description="setHeaders">
<property name="HTTP_SC" scope="axis2" type="STRING" value="200"/>
<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="OK"/>
<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/>
<property name="messageType" scope="axis2" type="STRING" value="text/xml"/>
<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/>
<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/>
<property name="RESPONSE" scope="default" type="STRING" value="true"/>
</propertyGroup>
</sequence>
Конечно, удаляем все ненужные транспортные заголовки из REST API, как и в прошлом случае.
Обязательно нужно сделать и последовательность для HealthCheck — проверку готовности сервиса принимать траффик (Readness Probe). Он пригодится, когда мы будем деплоить всё в Kubernetes или OpenShift.
<sequence name="Rosstat_HealthRequest_Sequence" onError="Rosstat_FaultSequence" trace="disable" xmlns="http://ws.apache.org/ns/synapse">
<propertyGroup description="removeHeaders">
<property action="remove" name="TRANSPORT_HEADERS" scope="axis2"/>
<property action="remove" name="EXCESS_TRANSPORT_HEADERS" scope="axis2"/>
</propertyGroup>
<payloadFactory description="SOAPResponse" media-type="xml">
<format>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<p:HealthResponse xmlns:p="http://ru.rosbank.servicemix/cd/client">
<p:status>OK</p:status>
</p:HealthResponse>
</soapenv:Body>
</soapenv:Envelope>
</format>
<args/>
</payloadFactory>
<propertyGroup description="setHeaders">
<property name="HTTP_SC" scope="axis2" type="STRING" value="200"/>
<property name="HTTP_SC_DESC" scope="axis2" type="STRING" value="OK"/>
<property name="MESSAGE_FORMAT" scope="default" type="STRING" value="soap11"/>
<property name="messageType" scope="axis2" type="STRING" value="text/xml"/>
<property name="ContentType" scope="axis2" type="STRING" value="text/xml"/>
<property name="Content-Type" scope="transport" type="STRING" value="text/xml;charset=UTF-8"/>
<property name="RESPONSE" scope="default" type="STRING" value="true"/>
</propertyGroup>
<class description="InResLoggerSOAP" name="ru.rosbank.mediator.LoggerMediatorSOAP">
<property name="logLevel" value="INFO"/>
<property name="ReqType" value="InResponse"/>
<property name="isLogHeader" value="true"/>
<property name="isLogPayload" value="true"/>
</class>
</sequence>
Далее создадим endpoint для вызова внешних сервисов. Здесь всё так же, как и с REST API.
<endpoint name="Rosstat_GetOrganizationInfo_EP" xmlns="http://ws.apache.org/ns/synapse">
<http method="post" uri-template="{uri.var.endpointURL_FullAddress}">
<timeout>
<duration>40000</duration>
<responseAction>fault</responseAction>
</timeout>
<suspendOnFailure>
<errorCodes>101500,101501,101506,101507,101508</errorCodes>
<initialDuration>1000</initialDuration>
<progressionFactor>2.0</progressionFactor>
<maximumDuration>60000</maximumDuration>
</suspendOnFailure>
<markForSuspension>
<errorCodes>101503,101504,101505</errorCodes>
<retriesBeforeSuspension>3</retriesBeforeSuspension>
<retryDelay>100</retryDelay>
</markForSuspension>
</http>
</endpoint>
Добавляем WSDL-файл и XSD-схемы
В модуле RosstatServiceRegistryResources (в нашем примере) выбираем New – RegistryResource и в появившемся окне — From existing template.
Далее выставляем параметры.
ResourceName: RosstatOrganizationInfo (указываем имя файла WSDL без расширения)
ArtifactName: RosstatOrganizationInfoWSDL (то же самое, что и для ResourceName, только добавляем в конце суффикс WSDL)
Template: выбираем из списка значение WSDL File
Registry: выбираем из списка значение conf
RegistryPath: resources/rosstat/wsdl
Аналогичным образом добавляем XSD-схемы.
ResourceName: RosstatOrganizationInfo (указываем имя файла XSD без расширения)
ArtifactName: RosstatOrganizationInfoXSD (то же самое, что и для ResourceName, только добавляем в конце суффикс XSD)
Template: выбираем из списка значение XSD File
Registry: выбираем из списка значение conf
RegistryPath: resources/rosstat/wsdl
Создаем ProxyService
С SOAP и другими протоколами, кроме REST API, используется ProxyService. Сделаем новый ProxyService, используя ранее созданные Template, Sequence, Endpoint.
Выбираем тип Custom Proxy и указываем транспортные протоколы http и https:
В режиме Design ProxyService будет выглядеть вот так:
Атрибут name у корневого элемента proxy является именем SOAP-сервиса. Значение должно совпадать с тем, что будет в URL после /services/: https://localhost:8253/services/RosstatService?wsdl. Мы также добавили в код валидацию запроса по XSD:
<proxy name="RosstatService" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
<target>
<inSequence>
<sequence key="Rosstat_InboundSequence"/>
<validate cache-schema="true">
<schema key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.xsd"/>
<on-fail>
<sequence key="Rosstat_FaultSequence"/>
</on-fail>
</validate>
<switch source="$ctx:ENV_METHOD_NAME">
<case regex="RosstatOrganizationInfoRequest">
<sequence key="Rosstat_GetOrganizationInfo_Sequence"/>
</case>
<case regex="HealthRequest">
<sequence key="Rosstat_HealthRequest_Sequence"/>
</case>
<default>
<sequence key="Rosstat_UnsupportedService_Sequence"/>
</default>
</switch>
<sequence key="Rosstat_OutboundSequence"/>
<respond/>
</inSequence>
<outSequence/>
<faultSequence>
<sequence key="Rosstat_FaultSequence"/>
</faultSequence>
</target>
<publishWSDL key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.wsdl" preservePolicy="true">
<resource key="conf:resources/rosstat/wsdl/RosstatOrganizationInfo.xsd" location="RosstatOrganizationInfo.xsd"/>
</publishWSDL>
<parameter name="disableREST">true</parameter>
<parameter name="disableSOAP12">true</parameter>
</proxy>
В блоке "publishWSDL" нужно обязательно указать ссылки на WSDL-файл и XSD-схемы (если их несколько), которые были добавлены в проект на предыдущем шаге. Это необходимо как раз для валидации и получения корректных файлов WSDL/XSD через адрес https://localhost:8253/services/RosstatService?wsdl.
Если сервис должен работать по SOAP 1.1/1.2, необходимо отключить REST и ненужную версию протокола SOAP.
Для SOAP 1.1:
<parameter name="disableREST">true</parameter>
<parameter name="disableSOAP12">true</parameter>
Для SOAP 1.2:
<parameter name="disableREST">true</parameter>
<parameter name="disableSOAP11">true</parameter>
Собираем сервис
Открывает pom-файл в модуле RosstatServiceCompositeExporter и выбираем все созданные артефакты в модулях RosstatServiceConfig и RosstatServiceRegistryResources:
Сборка сервиса через Maven организована так же, как и в случае с REST API.
Дальнейшие действия зависят от конкретного проекта. Как я указывал в прошлой статье, мы пишем интеграционные тесты с использованием Postman и заглушки для BackendService с Wiremock.
Если у вас появились вопросы по разработке на WSO2, буду рад ответить на них в комментариях. Мы планируем продолжать серию публикаций по этой платформе, так что подписывайтесь, если интересно, и пишите, что еще хотели бы о ней узнать.