/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* Copyright (c) 2013, MPL CodeInside http://codeinside.ru
*/
package ru.codeinside.gws.core.sproto;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import ru.codeinside.gws.api.CryptoProvider;
import ru.codeinside.gws.api.Enclosure;
import ru.codeinside.gws.api.Packet;
import ru.codeinside.gws.api.Revision;
import ru.codeinside.gws.api.ServerLog;
import ru.codeinside.gws.api.ServerProtocol;
import ru.codeinside.gws.api.ServerRequest;
import ru.codeinside.gws.api.ServerResponse;
import ru.codeinside.gws.api.ServiceDefinition;
import ru.codeinside.gws.api.XmlNormalizer;
import ru.codeinside.gws.api.XmlSignatureInjector;
import ru.codeinside.gws.core.Xml;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPEnvelope;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Map;
import java.util.logging.Logger;
/**
* В тестах можно включить дамп: HttpAdapter.dump = false;
*/
public class ServerProtocolImpl implements ServerProtocol {
private final Logger logger = Logger.getLogger(ServerProtocolImpl.class.getName());
private final String REV;
private final CryptoProvider cryptoProvider;
private final Revision revision;
private final XmlNormalizer xmlNormalizer;
private final XmlSignatureInjector injector;
public ServerProtocolImpl(final CryptoProvider cryptoProvider, String REV, Revision revision, XmlNormalizer xmlNormalizer, XmlSignatureInjector injector) {
this.cryptoProvider = cryptoProvider;
this.REV = REV;
this.revision = revision;
this.xmlNormalizer = xmlNormalizer;
this.injector = injector;
}
@Override
public Revision getRevision() {
return revision;
}
//TODO: как осуществлять возврат ошибок?
@Override
public ServerRequest processRequest(final SOAPMessage message, final QName service, final ServiceDefinition.Port port) {
try {
final SOAPBody soapBody = message.getSOAPBody();
if ("Fault".equals(soapBody.getNodeName())
&& "http://www.w3.org/2003/05/soap-envelope".equals(soapBody.getNamespaceURI())) {
// TODO: разобрать и вернуть ошибку!
throw new UnsupportedOperationException();
}
final ServerRequest serverRequest = new ServerRequest();
Element action = Xml.parseAction(soapBody);
QName inPart = new QName(action.getNamespaceURI(), action.getLocalName());
ServiceDefinition.Operation operation = null;
findOperation:
for (final Map.Entry<QName, ServiceDefinition.Operation> op : port.operations.entrySet()) {
final ServiceDefinition.Operation value = op.getValue();
for (QName name : value.in.parts.values()) {
if (name.equals(inPart)) {
operation = value;
serverRequest.action = op.getKey();
break findOperation;
}
}
}
if (operation == null) {
// TODO: с WSDL схемой это невозможно
throw new IllegalArgumentException("Invalid operation " + inPart + " for port " + port.port + " in service " + service);
}
serverRequest.verifyResult = cryptoProvider.verify(message);
serverRequest.routerPacket = Xml.parseRouterPacket(message.getSOAPHeader(), revision);
serverRequest.packet = Xml.parseSmevMessage(action, revision);
final Xml.MessageDataContent mdc = Xml.processMessageData(message, action, revision, cryptoProvider);
serverRequest.docRequestCode = mdc.requestCode;
serverRequest.appData = mdc.appData;
serverRequest.attachmens = mdc.attachmens;
return serverRequest;
} catch (SOAPException e) {
throw new RuntimeException(e);
}
}
@Override
public SOAPMessage processResponse(
ServerRequest request, ServerResponse response, QName service, ServiceDefinition.Port port, ServerLog serverLog) {
if (response.responseMessage != null && response.responseMessage.length > 0) {
try {
MessageFactory messageFactory = MessageFactory.newInstance();
return messageFactory.createMessage(new MimeHeaders(), new ByteArrayInputStream(response.responseMessage));
} catch (SOAPException e) {
logger.severe("Unable deserialize SOAPMessage from bytes (SOAPException): " + e.getMessage());
throw new RuntimeException(e);
} catch (IOException e) {
logger.severe("Unable deserialize SOAPMessage from bytes (IOException): " + e.getMessage());
throw new RuntimeException(e);
}
}
return getLegacySoapMessage(request, response, service, port, serverLog);
}
private SOAPMessage getLegacySoapMessage(
ServerRequest request, ServerResponse response, QName service, ServiceDefinition.Port port,
ServerLog serverLog) {
if (request != null) {
processChain(request, response);
}
if (serverLog != null) {
serverLog.logResponse(response);
}
final SOAPMessage out = buildSoapMessage(response, service, port);
cryptoProvider.sign(out);
return out;
}
@Override
public SOAPMessage createMessage(
ServerResponse response,
QName service, ServiceDefinition.Port port,
ServerLog serverLog,
OutputStream normalizedSignedInfo) {
if (serverLog != null) {
serverLog.logResponse(response);
}
try {
SOAPMessage soapMessage = buildSoapMessage(response, service, port);
//на случай, если в маршруте нет этапа подписи ЭП-ОВ
if (normalizedSignedInfo == null) {
return soapMessage;
}
ByteArrayOutputStream normalizedBody = new ByteArrayOutputStream();
xmlNormalizer.normalize(soapMessage.getSOAPBody(), normalizedBody);
ByteArrayInputStream normalizedBodyIS = new ByteArrayInputStream(normalizedBody.toByteArray());
byte[] normalizedBodyDigest = cryptoProvider.digest(normalizedBodyIS);
injector.prepareSoapMessage(soapMessage, normalizedBodyDigest);
Element signedInfo = (Element) soapMessage.getSOAPHeader().getFirstChild().getFirstChild().getFirstChild();
xmlNormalizer.normalize(signedInfo, normalizedSignedInfo);
return soapMessage;
} catch (SOAPException e) {
e.printStackTrace();
throw new IllegalStateException("Ошибка нормализации тела SOAP-сообщения", e);
}
}
private void processChain(final ServerRequest request, final ServerResponse response) {
final Packet requestPacket = request.packet;
final Packet responsePacket = response.packet;
if (response.action == null) {
response.action = request.action;
}
// перевернём отправителя и получателя
responsePacket.sender = requestPacket.recipient;
responsePacket.recipient = requestPacket.sender;
responsePacket.originator = requestPacket.originator;
// дата обработки
if (responsePacket.date == null) {
responsePacket.date = new Date();
}
// начало цепочки запросов (обычно поставщик должен обеспечить!)
if (responsePacket.originRequestIdRef == null) {
if (requestPacket.originRequestIdRef != null) {
// связываем с запросом
responsePacket.originRequestIdRef = requestPacket.originRequestIdRef;
} else if (request.routerPacket != null) {
// связываем с ID присвоенным роутером
responsePacket.originRequestIdRef = request.routerPacket.messageId;
} else {
// без роутера используем ID запроса.
responsePacket.originRequestIdRef = requestPacket.requestIdRef;
}
}
// цепочка запросов
if (responsePacket.requestIdRef == null) {
if (request.routerPacket != null) {
// связываем с ID присвоенным роутером
responsePacket.requestIdRef = request.routerPacket.messageId;
} else {
// без роутера используем ID запроса.
responsePacket.requestIdRef = requestPacket.requestIdRef;
}
}
// тип ответа
if (responsePacket.exchangeType == null) {
responsePacket.exchangeType = requestPacket.exchangeType;
}
}
// TODO: проверки ЗА протокол в тесты
private ServiceDefinition.Operation getOperation(final QName service, final ServiceDefinition.Port port, final QName action) {
final ServiceDefinition.Operation operation = port.operations.get(action);
if (operation == null || operation.in == null || operation.out == null) {
throw new IllegalArgumentException("Invalid operation " + action + " for port " + port.port + " in service " + service);
}
if (operation.in.parts == null || operation.in.parts.size() != 1) {
throw new IllegalArgumentException("Invalid parts operation " + action + " for port " + port.port + " in service " + service);
}
if (operation.out.parts == null || operation.out.parts.size() != 1) {
throw new IllegalArgumentException("Invalid parts operation " + action + " for port " + port.port + " in service " + service);
}
return operation;
}
private SOAPMessage buildSoapMessage(ServerResponse response, final QName service, final ServiceDefinition.Port port) {
final ServiceDefinition.Operation operation = getOperation(service, port, response.action);
SOAPMessage soapMessage;
try {
soapMessage = MessageFactory.newInstance().createMessage();
final SOAPEnvelope envelope = soapMessage.getSOAPPart().getEnvelope();
envelope.addNamespaceDeclaration("smev", REV);
envelope.addNamespaceDeclaration("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
final SOAPBody body = envelope.getBody();
// TODO: перенести это в провайдер криптографии?
body.addAttribute(envelope.createQName("Id", "wsu"), "body");
final QName outArg = operation.out.parts.values().iterator().next();
SOAPBodyElement action = body.addBodyElement(envelope.createName(outArg.getLocalPart(), "SOAP-WS", outArg.getNamespaceURI()));
Xml.fillSmevMessageByPacket(action, response.packet, revision);
final Enclosure[] enclosures = response.attachmens == null ? null : response.attachmens.toArray(new Enclosure[response.attachmens.size()]);
Xml.addMessageData(response.appData, response.docRequestCode, enclosures, action, soapMessage.getSOAPPart(), cryptoProvider, revision);
} catch (SOAPException e) {
throw new IllegalStateException(e);
} catch (DatatypeConfigurationException e) {
throw new IllegalStateException(e);
} catch (SAXException e) {
throw new IllegalStateException(e);
} catch (ParserConfigurationException e) {
throw new IllegalStateException(e);
} catch (IOException e) {
throw new IllegalStateException(e);
}
return soapMessage;
}
}