Parsing CreditOrgInfo web sevice – Получение информации по кредитным организациям

Интересная задача от Алексея. Есть организация Центральный банк РФ, предоставляющая web service SOAP, по кредитным организациям.

Задание:
Используя SOAP сервис получить информацию по БИК всех банков России. Распарсить XML и сохранить в БД.

Использовал:

Spring jdbc PostgreSQL SOAPConnection
XPath SOAP XML

Сам web service опубликован тут: CreditOrgInfo WSDL
Сервис предоставляет множество методов. Нам необходим EnumBIC_XML. Метод на входе имеет пустое сообщение:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://web.cbr.ru/">
   <soapenv:Header/>
   <soapenv:Body>
      <web:EnumBIC_XML/>
   </soapenv:Body>
</soapenv:Envelope>

На выходе возвращает XML:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <EnumBIC_XMLResponse xmlns="http://web.cbr.ru/">
         <EnumBIC_XMLResult>
            <EnumBIC xmlns="">
               <BIC>
                  <BIC>040173725</BIC>
                  <RC>1993-06-17T00:00:00+04:00</RC>
                  <NM>АЛТАЙБИЗНЕС-БАНК</NM>
                  <RB>1022200526446</RB>
                  <RN>2388</RN>
                  <intCode>10000001</intCode>
               </BIC>
               <BIC>
                  <BIC>040173745</BIC>
                  <RC>1992-08-21T00:00:00+04:00</RC>
                  <NM>СИБСОЦБАНК</NM>
                  <RB>1022200525819</RB>
                  <RN>2015</RN>
                  <intCode>10000012</intCode>
               </BIC>
            </EnumBIC>
         </EnumBIC_XMLResult>
      </EnumBIC_XMLResponse>
   </soap:Body>
</soap:Envelope>

Также опубликована XML Schema.

Множество подходов есть к решению данной задачи. Первое что приходит в голову, по опубликованной WSDL нагенерить классы, собрать клиента и с помощью Spring получить ответ. Есть шикарная утилита wsimport, как с помощью нее генерить классы описывал тут.
Однако сразу сталкиваемся с проблемой, как и многие на stackoverflow:
[ERROR] undefined element declaration ‘s:schema’ line 55

Дело в том, что предоставляемая wsdl CreditOrgInfo.asmx сгенерирована автоматически и на .NET – включает в себя неопределенный элемент

<s:element name="EnumBICResponse">
	<s:complexType>
		<s:sequence>
			<s:element minOccurs="0" maxOccurs="1" name="EnumBICResult">
				<s:complexType>
					<s:sequence>
						<s:element ref="s:schema"/>
						<s:any/>
					</s:sequence>
				</s:complexType>
			</s:element>
		</s:sequence>
	</s:complexType>
</s:element>

Из за которого wsimport и не может сгенерировать классы. Есть несколько способов обойти данную проблему:
– Использовать генерацию с параметризацией через customization.xjb (на GIT выложу в проекте настройки для этого способа)
– Либо, скачать WSDL локально, отредактировать (банально удалить элемент s:schema) и сгенерировать классы.
Оба способа работаю, но дальше возникает следующая делема, во первых сервис имеет более 50 методов, во вторых каждый метод в спецификации 1.1 и 1.2.
Итого после успешной генерации получаем проект в сотни неиспользуемых классов, в которых придется разбираться и искать необходимые.
Ну и вишенкой на торте, те в третьих, после генерации оказывается, что элементы для парсинга нашей XML попросту не описаны в WSDL и при генерации классов будет просто 1 объект.
Т.е. придется еще вручную описать JAXB классы для удобного размаршаливания и получения данных, кто описывал XML с большим количеством namespace знает, то еще занятие. (пример будет на GIT)

Итак, выбираем следующий способ:
SOAP сообщение отправляем с помощью пакета SOAPConnectionFactory и MessageFactory.
Полученную в ответе XML парсим XPATH попутно собираю Map и сохраняем в БД PostgreSQL. Обходимся одним классом, без всяких танцев с бубном.

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

CREATE TABLE bicList(
  id serial PRIMARY KEY,
bic VARCHAR (50) UNIQUE NOT NULL,
rc VARCHAR (50) NOT NULL,
nm VARCHAR (50) NOT NULL,
rb VARCHAR (50) NOT NULL,
rn VARCHAR (50) NOT NULL,
intCode VARCHAR (50) NOT NULL
);

Далее создаем сообщение:

public static void main(String args[]) {
        String soapEndpointUrl = "http://www.cbr.ru/CreditInfoWebServ/CreditOrgInfo.asmx";
        String soapAction = "http://web.cbr.ru/EnumBIC_XML";
        callSoapWebService(soapEndpointUrl, soapAction);
    }
 
    private static void createSoapEnvelope(SOAPMessage soapMessage) throws SOAPException {
        SOAPPart soapPart = soapMessage.getSOAPPart();
 
        String myNamespace = "s";
        String myNamespaceURI = "http://www.cbr.ru/";
 
        // SOAP Envelope
        SOAPEnvelope envelope = soapPart.getEnvelope();
        envelope.addNamespaceDeclaration(myNamespace, myNamespaceURI);
    }
 
private static SOAPMessage createSOAPRequest(String soapAction) throws Exception {
	MessageFactory messageFactory = MessageFactory.newInstance();
	SOAPMessage soapMessage = messageFactory.createMessage();
 
	createSoapEnvelope(soapMessage);
 
	MimeHeaders headers = soapMessage.getMimeHeaders();
	headers.addHeader("SOAPAction", soapAction);
 
	soapMessage.saveChanges();
	return soapMessage;
}

XPATH проходим по XML:

DocumentBuilderFactory dbf =
DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xmlString));
 
Document doc = db.parse(is);
 
// Create XPathFactory object
XPathFactory xpathFactory = XPathFactory.newInstance();
 
// Create XPath object
XPath xpath = xpathFactory.newXPath();
 
int i;
for (i = 1; i < 832; i++) {
XPathExpression expr =
	xpath.compile("//*[local-name()='BIC'][" + i + "]" + "//*[local-name()='BIC'][\" +i+ \"]");
String nodes = (String) expr.evaluate(doc, XPathConstants.STRING);

Собираем MAP и записываем в БД:

// Creating map with all required params
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("id", i);
paramMap.put("bic", nodes);
paramMap.put("rc", nodes2);
paramMap.put("nm", nodes3);
paramMap.put("rb", nodes4);
paramMap.put("rn", nodes5);
paramMap.put("intcode", nodes6);
 
// Passing map containing named params
nqu.update(INSERT_QUERY, paramMap);

Удачного кодинга.

Ссылка на полную версию проекта на GitHub:
Github CreditOrgInfoProject

Releated Post